Compare commits
12
Commits
feat/monorepo
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
975b8e546c | ||
|
|
10e453e4be | ||
|
|
ee8258556d | ||
|
|
2a45eef366 | ||
|
|
7399812d0e | ||
|
|
dfd8a82809 | ||
|
|
a8a34075c7 | ||
|
|
bcb00dbe65 | ||
|
|
685a3e5206 | ||
|
|
c7884480c5 | ||
|
|
cd5f123270 | ||
|
|
fa61a76444 |
+5
-3
@@ -1,6 +1,8 @@
|
||||
**/node_modules
|
||||
**/dist
|
||||
**/.output
|
||||
node_modules
|
||||
apps/*/node_modules
|
||||
packages/*/node_modules
|
||||
# **/dist
|
||||
# **/.output
|
||||
**/.nuxt
|
||||
**/.turbo
|
||||
**/*.tsbuildinfo
|
||||
|
||||
@@ -29,6 +29,3 @@ REVALIDATE_SECRET=change-me-too
|
||||
ADMIN_PHONE=09120000000
|
||||
ADMIN_PASSWORD=change-me
|
||||
ADMIN_NAME=مدیر سایت
|
||||
|
||||
# npm registry used while building the images (set a mirror if npmjs.org is slow or blocked)
|
||||
NPM_REGISTRY=https://registry.npmjs.org/
|
||||
|
||||
+12
-4
@@ -1,9 +1,11 @@
|
||||
# dependencies
|
||||
node_modules/
|
||||
# dependencies (build outputs keep theirs: apps/web/.output/server/node_modules, apps/api/.output/node_modules)
|
||||
/node_modules/
|
||||
/apps/*/node_modules/
|
||||
/packages/*/node_modules/
|
||||
|
||||
# build output
|
||||
dist/
|
||||
.output/
|
||||
# dist/
|
||||
# .output/
|
||||
.nuxt/
|
||||
.data/
|
||||
.turbo/
|
||||
@@ -27,3 +29,9 @@ pnpm-debug.log*
|
||||
|
||||
# database dumps from the backup service
|
||||
backups/
|
||||
|
||||
# local s3 mock storage
|
||||
.s3data/
|
||||
**/.s3data/
|
||||
*.jpg
|
||||
icons (1).json
|
||||
|
||||
@@ -126,21 +126,16 @@ docker compose exec api node dist/seed/seed.js # فقط بار اول
|
||||
```
|
||||
|
||||
- کانتینر `gateway` روی پورت `HTTP_PORT` (پیشفرض 8080) گوش میدهد و هر دو دامنه (`SITE_DOMAIN` و `ADMIN_DOMAIN`) را سرو میکند. TLS را nginx یا Traefik روی سرور یا CDN جلوی آن تأمین میکند. هر دو دامنه باید به سرور اشاره کنند.
|
||||
- سرویس یکبارهی `migrate` پیش از `api` اجرا میشود، migrationهای جدید را اعمال میکند و خارج میشود. Prisma CLI فقط در همین image هست و image اجرایی API سبک میماند.
|
||||
- سرویس یکبارهی `migrate` پیش از `api` اجرا میشود، migrationهای جدید را اعمال میکند و خارج میشود. این کار را `dist/migrate.js` انجام میدهد که همارز `prisma migrate deploy` است و از همان جدول `_prisma_migrations` استفاده میکند، پس Prisma CLI روی سرور لازم نیست.
|
||||
- نام پروژهی Compose ثابت و برابر `imen` است، تا با `docker-compose.dev.yml` (PostgreSQL و MinIO توسعه) هیچ کانتینر یا volume مشترکی نداشته باشد.
|
||||
- سرویس `backup` هر روز یک dump از دیتابیس در `./backups` میسازد و dumpهای قدیمیتر از ۱۴ روز را پاک میکند. بازگردانی:
|
||||
`docker compose exec -T postgres pg_restore -U imen -d imen --clean < backups/imen-YYYY-MM-DD.dump`
|
||||
- از فایلهای MinIO (volume `miniodata`) هم جداگانه بکاپ بگیرید.
|
||||
|
||||
**اگر سرور به رجیستری npm دسترسی ندارد**، دو راه هست:
|
||||
1. در `.env` مقدار `NPM_REGISTRY` را روی یک mirror در دسترس بگذارید.
|
||||
2. imageها را روی سیستم توسعه بسازید و به سرور منتقل کنید:
|
||||
```bash
|
||||
docker compose build
|
||||
docker save imen-api imen-migrate imen-web imen-admin | gzip > imen-images.tar.gz
|
||||
# روی سرور:
|
||||
gunzip -c imen-images.tar.gz | docker load && docker compose up -d
|
||||
```
|
||||
**روی سرور چیزی نصب یا build نمیشود.** imageها فقط خروجی آمادهای را که در ریپو commit شده کپی میکنند، پس سرور به رجیستری npm نیازی ندارد. قبل از هر push روی سیستم توسعه `pnpm build` بگیرید و خروجیها را هم commit کنید:
|
||||
- `apps/web/.output` (همراه `server/node_modules`)، `apps/admin/dist` و `apps/api/.output`.
|
||||
- `apps/api/.output` را `apps/api/scripts/bundle.mjs` میسازد. کد API و همهی وابستگیهای JS در آن bundle میشوند و فقط پکیجهای native (`sharp` و `@node-rs/argon2`) با باینری Linux (Alpine، x64) کنارش کپی میشوند. این باینریها را `supportedArchitectures` در `pnpm-workspace.yaml` موقع `pnpm install` دانلود میکند.
|
||||
- آدرس سایت در لینکهای پنل ادمین موقع build از `apps/admin/.env.production` خوانده میشود.
|
||||
|
||||
**Object Storage ابرآروان (یا هر S3 دیگر) بهجای MinIO**: در سرویس `api`، مقادیر `S3_ENDPOINT`، `S3_BUCKET` و کلیدها را تنظیم کنید و `S3_ENSURE_BUCKET` را `false` بگذارید. `MEDIA_PUBLIC_URL` را روی آدرس عمومی باکت یا CDN بگذارید. بعد از آن به سرویس `minio` و مسیر `/media` در gateway نیازی نیست.
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# loaded by `vite build` (the pre-built dist/ the admin image serves); dev uses http://localhost:3000
|
||||
VITE_SITE_URL=https://imenpishgaman.ir
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{b as e}from"./VuesaxIcon-B2OgulS6.js";import{Et as t,It as n,Nt as r,Ot as i,Tt as a,_t as o,dt as s,i as c,kt as l,lt as u,ot as d,r as f,yt as p}from"./index-BuGK6ptM.js";var m=Symbol(`form-errors`),h=e=>Object.fromEntries(e.map(e=>[e.path,e.message]));function g(r,i){let a=n(structuredClone(i)),o=n({});t(m,o);function s(){let t=r.safeParse(a.value);return t.success?(o.value={},t.data):(o.value=h(e(t.error)),d.error(`لطفاً خطاهای فرم را برطرف کنید`),null)}function l(e){let t=f(e).issues;t?.length&&(o.value=h(t)),d.error(c(e))}function u(e){a.value=structuredClone(e),o.value={}}return{model:a,errors:o,validate:s,handleError:l,reset:u}}var _=o({__name:`FormField`,props:{path:{},label:{},hint:{},required:{type:Boolean}},setup(e){let t=e,n=p(m,null),o=u(()=>n?.value[t.path]);return(t,n)=>{let c=l(`a-form-item`);return a(),s(c,{label:e.label,required:e.required,"validate-status":o.value?`error`:void 0,help:o.value||e.hint},{default:r(()=>[i(t.$slots,`default`)]),_:3},8,[`label`,`required`,`validate-status`,`help`])}}});export{g as n,_ as t};
|
||||
@@ -0,0 +1 @@
|
||||
import{t as e,y as t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,Lt as r,Nt as i,Tt as a,_t as o,bt as s,dt as c,gt as l,jt as u,kt as d,pt as f,ut as p}from"./index-BuGK6ptM.js";var m={class:`flex items-center gap-2`},h={class:`flex size-9 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-[#ff5a00]`},g={key:1,class:`text-xs text-gray-400`},_={class:`flex items-center gap-2.5 py-0.5`,dir:`ltr`},v={class:`text-xs font-mono`},y=o({__name:`IconSelect`,props:s({allowClear:{type:Boolean}},{modelValue:{required:!0},modelModifiers:{}}),emits:[`update:modelValue`],setup(o){let s=u(o,`modelValue`),y=t.map(e=>({value:e,label:e}));return(t,u)=>{let b=d(`a-select`);return a(),f(`div`,m,[p(`span`,h,[s.value?(a(),c(e,{key:0,name:s.value,size:20},null,8,[`name`])):(a(),f(`span`,g,`---`))]),l(b,{value:s.value,"onUpdate:value":u[0]||=e=>s.value=e,class:`min-w-0 flex-1`,"show-search":``,"allow-clear":o.allowClear,options:r(y),placeholder:`انتخاب آیکون`},{option:i(({value:t})=>[p(`span`,_,[l(e,{name:t,size:18,class:`text-orange-600`},null,8,[`name`]),p(`span`,v,n(t),1)])]),_:1},8,[`value`,`allow-clear`,`options`])])}}});export{y as t};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.login-grid[data-v-2d323d25]{background-image:linear-gradient(270deg,#edede60b 1px,#0000 1px),linear-gradient(#edede60b 1px,#0000 1px);background-size:72px 72px}.login-tape[data-v-2d323d25]{background-image:repeating-linear-gradient(45deg,#ffb800 0 10px,#0b1224 10px 20px)}.login-noise[data-v-2d323d25]{opacity:.045;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}.login-fade-1[data-v-2d323d25],.login-fade-3[data-v-2d323d25]{animation:.9s cubic-bezier(.22,1,.36,1) both login-fade-up-2d323d25}.login-fade-1[data-v-2d323d25]{animation-delay:.12s}.login-fade-3[data-v-2d323d25]{animation-delay:.28s}@keyframes login-fade-up-2d323d25{0%{opacity:0;transform:translateY(36px)}to{opacity:1;transform:translateY(0)}}.login-page[data-v-2d323d25]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}.login-panel[data-v-2d323d25] .ant-form-item-label>label{color:#edede6!important}.login-panel[data-v-2d323d25] .ant-input-affix-wrapper,.login-panel[data-v-2d323d25] .ant-input{color:#edede6!important;background:#152038!important;border-color:#2a3d63!important}.login-panel[data-v-2d323d25] .ant-input-affix-wrapper input.ant-input,.login-panel[data-v-2d323d25] .ant-input-affix-wrapper input{color:#edede6!important;background:0 0!important}.login-panel[data-v-2d323d25] .ant-input-affix-wrapper:hover,.login-panel[data-v-2d323d25] .ant-input-affix-wrapper-focused,.login-panel[data-v-2d323d25] .ant-input-affix-wrapper:focus-within{border-color:#ff5a00!important;box-shadow:0 0 0 2px #ff5a0026!important}.login-panel[data-v-2d323d25] .ant-input::placeholder,.login-panel[data-v-2d323d25] .ant-input-password-icon,.login-panel[data-v-2d323d25] .ant-input-suffix{color:#8e9ab0!important}.login-panel[data-v-2d323d25] input:-webkit-autofill{transition:background-color 99999s ease-in-out;-webkit-text-fill-color:#edede6!important;caret-color:#edede6!important;box-shadow:inset 0 0 0 1000px #152038!important}.login-panel[data-v-2d323d25] input:-webkit-autofill:hover{transition:background-color 99999s ease-in-out;-webkit-text-fill-color:#edede6!important;caret-color:#edede6!important;box-shadow:inset 0 0 0 1000px #152038!important}.login-panel[data-v-2d323d25] input:-webkit-autofill:focus{transition:background-color 99999s ease-in-out;-webkit-text-fill-color:#edede6!important;caret-color:#edede6!important;box-shadow:inset 0 0 0 1000px #152038!important}.login-panel[data-v-2d323d25] input:-webkit-autofill:active{transition:background-color 99999s ease-in-out;-webkit-text-fill-color:#edede6!important;caret-color:#edede6!important;box-shadow:inset 0 0 0 1000px #152038!important}.login-panel[data-v-2d323d25] .ant-form-item-explain-error{color:#ff7a2a}
|
||||
@@ -0,0 +1 @@
|
||||
import{c as e,n as t,t as n}from"./VuesaxIcon-B2OgulS6.js";import{It as r,Lt as i,Nt as a,Tt as o,_t as s,a as c,gt as l,ht as u,kt as d,mt as f,o as p,pt as m,st as h,t as g,ut as _}from"./index-BuGK6ptM.js";import"./logo-BuUh917n.js";import{n as v,t as y}from"./FormField-CiVT7mqC.js";var b={class:`login-page relative flex min-h-screen overflow-hidden text-[#edede6]`},x={class:`relative z-10 mx-auto grid w-full max-w-[1120px] flex-1 items-center gap-10 px-5 py-12 md:px-8 lg:grid-cols-12 lg:gap-16`},S={class:`login-fade-3 lg:col-span-6`},C={class:`login-panel w-full max-w-md border border-[#2a3d63]/70 bg-[#1a2744]/80 p-7 backdrop-blur-md md:p-8 lg:me-auto`},w=t(s({__name:`LoginPage`,setup(t){let s=g(),w=p(),T=c(),{model:E,validate:D,handleError:O}=v(e,{phone:``,password:``}),k=r(!1);async function A(){let e=D();if(e){k.value=!0;try{await s.login(e);let t=typeof T.query.redirect==`string`?T.query.redirect:`/`;await w.replace(t.startsWith(`/`)?t:`/`)}catch(e){O(e)}finally{k.value=!1}}}return(e,t)=>{let r=d(`a-input`),s=d(`a-input-password`),c=d(`a-button`),p=d(`a-form`);return o(),m(`div`,b,[t[5]||=f(`<div class="pointer-events-none absolute inset-0 bg-[#0b1224]" data-v-2d323d25></div><div class="login-grid pointer-events-none absolute inset-0 opacity-50" data-v-2d323d25></div><div class="pointer-events-none absolute -start-24 top-1/4 size-[420px] rounded-full bg-[#ff5a00]/15 blur-[120px]" data-v-2d323d25></div><div class="pointer-events-none absolute -end-16 bottom-0 size-[360px] rounded-full bg-[#ffb800]/10 blur-[100px]" data-v-2d323d25></div><div class="login-tape pointer-events-none absolute bottom-0 end-0 z-10 h-48 w-1.5 opacity-70" data-v-2d323d25></div><div class="login-noise pointer-events-none absolute inset-0 z-[5]" data-v-2d323d25></div>`,6),_(`div`,x,[_(`div`,S,[_(`div`,C,[t[3]||=_(`div`,{class:`mb-7 text-center`},[_(`div`,{class:`font-mono text-[10px] tracking-[0.28em] text-[#8e9ab0]`},`SIGN IN`),_(`h2`,{class:`mt-2 text-xl font-bold text-[#edede6]`},`ورود به پنل`),_(`p`,{class:`mt-1.5 text-sm text-[#8e9ab0]`},`شماره موبایل و رمز عبور خود را وارد کنید`)],-1),l(p,{class:`login-form`,layout:`vertical`,onSubmit:h(A,[`prevent`])},{default:a(()=>[l(y,{path:`phone`,label:`شماره موبایل`},{default:a(()=>[l(r,{value:i(E).phone,"onUpdate:value":t[0]||=e=>i(E).phone=e,size:`large`,class:`ltr-input`,autocomplete:`tel`,autofocus:``,inputmode:`tel`,placeholder:`09121234567`},{prefix:a(()=>[l(n,{name:`mobile`,size:16,class:`text-[#8e9ab0]`})]),_:1},8,[`value`])]),_:1}),l(y,{path:`password`,label:`رمز عبور`},{default:a(()=>[l(s,{value:i(E).password,"onUpdate:value":t[1]||=e=>i(E).password=e,size:`large`,class:`ltr-input`,autocomplete:`current-password`,placeholder:`••••••••`},{prefix:a(()=>[l(n,{name:`lock`,size:16,class:`text-[#8e9ab0]`})]),_:1},8,[`value`])]),_:1}),l(c,{type:`primary`,"html-type":`submit`,size:`large`,block:``,loading:k.value,class:`!mt-2 !h-12 !rounded-full !font-bold !shadow-[0_8px_32px_rgba(255,90,0,0.35)]`},{default:a(()=>[...t[2]||=[u(` ورود به پنل `,-1)]]),_:1},8,[`loading`])]),_:1})])]),t[4]||=f(`<div class="login-fade-1 lg:col-span-6" data-v-2d323d25><div class="inline-flex items-center gap-2.5 rounded-full border border-[#ff5a00]/30 bg-[#ff5a00]/10 px-4 py-2 backdrop-blur" data-v-2d323d25><span class="size-1.5 animate-pulse rounded-full bg-[#ff5a00]" data-v-2d323d25></span><span class="text-xs font-medium text-[#ff5a00]" data-v-2d323d25>پنل مدیریت ایمن پیشگامان</span></div><div class="mt-8 flex items-center gap-4" data-v-2d323d25><span class="flex size-14 items-center justify-center rounded-xl bg-white shadow-[0_0_28px_rgba(10,30,92,0.45)]" data-v-2d323d25><img src="/logo.png" alt="ایمن پیشگامان" class="size-9 object-contain" data-v-2d323d25></span><div class="leading-tight" data-v-2d323d25><div class="text-2xl font-black tracking-tight md:text-3xl" data-v-2d323d25>ایمن پیشگامان</div><div class="mt-1 font-mono text-[10px] tracking-[0.32em] text-[#8e9ab0]" data-v-2d323d25> IMEN PISHGAMAN CO. </div></div></div><h1 class="mt-8 text-3xl font-black leading-[1.25] md:text-5xl md:leading-[1.2]" data-v-2d323d25> ورود امن <span class="mt-2 inline-block -rotate-1 rounded-lg bg-[#ffb800] px-3 py-1 text-[#0b1224] shadow-[0_10px_40px_rgba(255,184,0,0.22)]" data-v-2d323d25> به سامانه </span></h1><p class="mt-6 max-w-md text-sm leading-8 text-[#edede6]/70 md:text-base md:leading-9" data-v-2d323d25> مدیریت محصولات، محتوا و پیامهای سایت — فقط برای کاربران مجاز. </p></div>`,1)])])}}}),[[`__scopeId`,`data-v-2d323d25`]]);export{w as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{n as e,t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,Nt as r,Ot as i,Tt as a,_t as o,dt as s,ft as c,gt as l,kt as u,pt as d,ut as f}from"./index-BuGK6ptM.js";var p={class:`page-header`},m={class:`page-header__main`},h={class:`min-w-0`},g={class:`page-header__title`},_={key:0,class:`page-header__subtitle`},v={key:0,class:`page-header__actions`},y=e(o({__name:`PageHeader`,props:{title:{},subtitle:{},back:{}},setup(e){return(o,y)=>{let b=u(`RouterLink`);return a(),d(`div`,p,[f(`div`,m,[e.back?(a(),s(b,{key:0,to:e.back,class:`page-header__back`,title:`بازگشت`},{default:r(()=>[l(t,{name:`arrow-right`,size:18})]),_:1},8,[`to`])):c(``,!0),f(`div`,h,[f(`h1`,g,n(e.title),1),e.subtitle?(a(),d(`p`,_,n(e.subtitle),1)):c(``,!0)])]),o.$slots.default?(a(),d(`div`,v,[i(o.$slots,`default`,{},void 0,!0)])):c(``,!0)])}}}),[[`__scopeId`,`data-v-c0b72af8`]]);export{y as t};
|
||||
@@ -0,0 +1 @@
|
||||
.page-header[data-v-c0b72af8]{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;display:flex;flex:none!important}.page-header__main[data-v-c0b72af8]{align-items:center;gap:8px;min-width:0;display:flex}.page-header__back[data-v-c0b72af8]{color:#475569;background:#f1f5f9;border-radius:8px;flex-shrink:0;justify-content:center;align-items:center;width:34px;height:34px;text-decoration:none;transition:all .15s;display:inline-flex}.page-header__back[data-v-c0b72af8]:hover{color:#0f172a;background:#e2e8f0}.page-header__title[data-v-c0b72af8]{color:#0f172a;margin:0;font-size:16px;font-weight:700;line-height:1.3}@media (width>=640px){.page-header__title[data-v-c0b72af8]{font-size:18px}}.page-header__subtitle[data-v-c0b72af8]{color:#94a3b8;margin:2px 0 0;font-size:12px;line-height:1.4}.page-header__actions[data-v-c0b72af8]{flex-wrap:wrap;align-items:center;gap:8px;display:flex}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.ap[data-v-7f365815]{flex-direction:column;height:100%;min-height:0;display:flex;overflow:hidden}.ap__head[data-v-7f365815]{background:#fff;border-bottom:1px solid #eef1f6;flex-shrink:0;padding:16px 16px 12px}.ap__body[data-v-7f365815]{flex:1;min-height:0;padding:16px;overflow:auto}.ap__body--locked[data-v-7f365815]{flex-direction:column;display:flex;overflow:hidden}@media (width<=1023px){.ap[data-v-7f365815]{height:auto;min-height:100%;overflow:visible}.ap__head[data-v-7f365815]{z-index:20;background:#fff;padding:10px 12px;position:sticky;top:52px;box-shadow:0 1px 3px #0f172a08}.ap__body[data-v-7f365815]{padding:12px;overflow:visible}.ap__body--locked[data-v-7f365815]{height:auto;overflow:visible}}.table-shell[data-v-a64d8be2],.table-shell[data-v-a64d8be2] .ant-table-wrapper,.table-shell[data-v-a64d8be2] .ant-spin-nested-loading,.table-shell[data-v-a64d8be2] .ant-spin-container{flex-direction:column;flex:1;height:100%;min-height:0;display:flex}.table-shell[data-v-a64d8be2] .ant-table{flex:1;min-height:0}.table-shell[data-v-a64d8be2] .ant-table-pagination{flex-shrink:0;margin:12px 0 0!important}.toolbar[data-v-a64d8be2]{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.toolbar__start[data-v-a64d8be2]{flex:1;align-items:center;gap:16px;min-width:0;display:flex}.toolbar__search[data-v-a64d8be2]{flex:1;min-width:200px;max-width:360px}.toolbar__new[data-v-a64d8be2]{margin-inline-start:auto}.filter-panel[data-v-a64d8be2]{flex-direction:column;gap:10px;display:flex}.filter-panel__hint[data-v-a64d8be2]{color:#94a3b8;margin:0;font-size:12px;line-height:1.6}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{o as e,s as t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,It as r,Lt as i,Nt as a,Tt as o,_t as s,ct as c,gt as l,ht as u,kt as d,n as f,ot as p,pt as m,st as h,t as g,ut as _}from"./index-BuGK6ptM.js";import{t as v}from"./PageHeader-3Ryvg2HD.js";import{n as y,t as b}from"./FormField-CiVT7mqC.js";var x={class:`grid max-w-4xl gap-4 md:gap-6 md:grid-cols-2`},S={class:`flex items-center gap-3.5 mb-4 pb-4 border-b border-gray-100`},C={class:`flex size-14 items-center justify-center rounded-2xl bg-gradient-to-tr from-[#ff5a00] to-amber-500 text-xl font-black text-white shadow-md shadow-orange-500/20`},w={class:`text-base font-bold text-gray-900`},T={class:`mt-0.5 text-xs font-mono text-gray-500`,dir:`ltr`},E={dir:`ltr`,class:`font-mono`},D=s({__name:`ProfilePage`,setup(s){let D=g(),{model:O,validate:k,handleError:A,reset:j}=y(t,{currentPassword:``,newPassword:``}),M=r(!1);async function N(){let e=k();if(e){M.value=!0;try{await f(`/admin/auth/change-password`,{method:`POST`,body:e}),p.success(`رمز عبور تغییر کرد`),j({currentPassword:``,newPassword:``})}catch(e){A(e)}finally{M.value=!1}}}return(t,r)=>{let s=d(`a-descriptions-item`),f=d(`a-tag`),p=d(`a-descriptions`),g=d(`a-card`),y=d(`a-input-password`),k=d(`a-button`),A=d(`a-form`);return o(),m(c,null,[l(v,{title:`حساب کاربری`,subtitle:`مشخصات فردی، نقش دسترسی و تغییر رمز عبور ورود به سامانه`}),_(`div`,x,[l(g,{title:`مشخصات کاربری`},{default:a(()=>[_(`div`,S,[_(`div`,C,n(i(D).user?.name?.slice(0,1)||`U`),1),_(`div`,null,[_(`div`,w,n(i(D).user?.name),1),_(`div`,T,n(i(D).user?.phone),1)])]),l(p,{column:1,size:`small`},{default:a(()=>[l(s,{label:`نام`},{default:a(()=>[u(n(i(D).user?.name),1)]),_:1}),l(s,{label:`موبایل`},{default:a(()=>[_(`span`,E,n(i(D).user?.phone),1)]),_:1}),l(s,{label:`نقش دسترسی`},{default:a(()=>[l(f,{color:i(D).user?.role===`ADMIN`?`orange`:`blue`},{default:a(()=>[u(n(i(D).user&&i(e)[i(D).user.role]),1)]),_:1},8,[`color`])]),_:1})]),_:1})]),_:1}),l(g,{title:`تغییر رمز عبور`},{default:a(()=>[l(A,{layout:`vertical`,onSubmit:h(N,[`prevent`])},{default:a(()=>[l(b,{path:`currentPassword`,label:`رمز فعلی`,required:``},{default:a(()=>[l(y,{value:i(O).currentPassword,"onUpdate:value":r[0]||=e=>i(O).currentPassword=e,class:`ltr-input h-10`,autocomplete:`current-password`},null,8,[`value`])]),_:1}),l(b,{path:`newPassword`,label:`رمز جدید`,hint:`حداقل ۸ کاراکتر`,required:``},{default:a(()=>[l(y,{value:i(O).newPassword,"onUpdate:value":r[1]||=e=>i(O).newPassword=e,class:`ltr-input h-10`,autocomplete:`new-password`},null,8,[`value`])]),_:1}),l(k,{type:`primary`,"html-type":`submit`,loading:M.value,class:`w-full md:w-auto h-10 !font-bold`},{default:a(()=>[...r[2]||=[u(` تغییر رمز ورود `,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})])],64)}}});export{D as default};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{d as e,f as t}from"./VuesaxIcon-B2OgulS6.js";import{Bt as n,Ct as r,Dt as i,Ft as a,It as o,Lt as s,Nt as c,Rt as l,Tt as u,_t as d,ct as f,dt as p,ft as m,gt as h,ht as g,i as _,kt as v,n as y,o as b,ot as x,pt as S,t as C,ut as w}from"./index-BuGK6ptM.js";import{a as T}from"./format-B9MYDQJN.js";import{t as E}from"./PageHeader-3Ryvg2HD.js";var D={class:`grid gap-4 sm:grid-cols-2 xl:grid-cols-3`},O={class:`flex items-center justify-between gap-2`},k=[`onClick`],A={class:`font-bold text-gray-800 group-hover:text-orange-600 transition-colors`},j={key:0,class:`flex items-center flex-shrink-0`},M=[`onClick`],N=[`onClick`],P={key:0},F=d({__name:`SectionsPage`,setup(d){let F=b(),I=C(),L=e.filter(e=>e!==`settings`||I.isAdmin),R=o(!0),z=a({hero:!0,tape:!0,stats:!0,productsIntro:!0,about:!0,services:!0,standards:!0,industries:!0,testimonials:!0,contact:!0,footer:!0}),B=a({}),V=a({});function H(e){return z[e]!==!1}function U(e){return B[e]??null}async function W(){R.value=!0;try{let e=await y(`/admin/sections`);for(let t of e)z[t.key]=t.enabled!==!1,B[t.key]=t.updatedAt}catch(e){x.error(_(e))}finally{R.value=!1}}async function G(e,n){if(e===`settings`)return;let r=z[e]!==!1;z[e]=n,V[e]=!0;try{let r=await y(`/admin/sections/${e}/visibility`,{method:`PATCH`,body:{enabled:n}});z[e]=r.enabled!==!1,B[e]=r.updatedAt,x.success(`وضعیت «${t[e].title}» به ${n?`نمایش`:`عدم نمایش`} تغییر یافت`)}catch(t){z[e]=r,x.error(_(t))}finally{V[e]=!1}}function K(e){F.push({name:`section-edit`,params:{key:e}})}return r(()=>{W()}),(e,r)=>{let a=v(`a-tag`),o=v(`a-switch`),d=v(`a-card`),_=v(`a-spin`);return u(),S(f,null,[h(E,{title:`بخشهای سایت`,subtitle:`متنها، تصاویر و فهرستهای صفحه اصلی؛ تنظیم وضعیت نمایش و ویرایش محتوا`}),h(_,{spinning:R.value},{default:c(()=>[w(`div`,D,[(u(!0),S(f,null,i(s(L),e=>(u(),S(`div`,{key:e,class:`block group`},[h(d,{hoverable:``,class:l([`h-full flex flex-col justify-between transition-all`,{"opacity-75 bg-gray-50/60":e!==`settings`&&!H(e)}])},{default:c(()=>[w(`div`,null,[w(`div`,O,[w(`div`,{class:`flex items-center gap-2 cursor-pointer flex-1`,onClick:t=>K(e)},[w(`span`,A,n(s(t)[e].title),1),e===`settings`?(u(),p(a,{key:0,color:`blue`},{default:c(()=>[...r[0]||=[g(`تنظیمات پایه`,-1)]]),_:1})):H(e)?m(``,!0):(u(),p(a,{key:1,color:`error`},{default:c(()=>[...r[1]||=[g(`مخفی`,-1)]]),_:1}))],8,k),e===`settings`?m(``,!0):(u(),S(`div`,j,[h(o,{checked:H(e),loading:V[e],"checked-children":`نمایش`,"un-checked-children":`مخفی`,onChange:t=>G(e,!!t)},null,8,[`checked`,`loading`,`onChange`])]))]),w(`p`,{class:`mt-2 text-sm leading-6 text-gray-500 cursor-pointer`,onClick:t=>K(e)},n(s(t)[e].desc),9,M)]),w(`div`,{class:`mt-4 flex items-center justify-between border-t border-gray-100 pt-3 text-xs text-gray-400 cursor-pointer`,onClick:t=>K(e)},[r[2]||=w(`span`,{class:`text-primary font-medium group-hover:underline`},`ویرایش بخش ←`,-1),U(e)?(u(),S(`span`,P,`آخرین تغییر: `+n(s(T)(U(e))),1)):m(``,!0)],8,N)]),_:2},1032,[`class`])]))),128))])]),_:1},8,[`spinning`])],64)}}});export{F as default};
|
||||
File diff suppressed because one or more lines are too long
+6203
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.vx-icon[data-v-9a78c900]{color:inherit;vertical-align:-.15em;flex-shrink:0;justify-content:center;align-items:center;line-height:0;display:inline-flex}.vx-icon[data-v-9a78c900] svg{width:100%;height:100%;display:block}
|
||||
+1
@@ -0,0 +1 @@
|
||||
var e=new Intl.DateTimeFormat(`fa-IR-u-ca-persian`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`}),t=t=>t?e.format(new Date(t)):`—`,n=e=>e<1048576?`${(e/1024).toLocaleString(`fa-IR`,{maximumFractionDigits:0})} کیلوبایت`:`${(e/1024/1024).toLocaleString(`fa-IR`,{maximumFractionDigits:1})} مگابایت`,r=e=>e.toLocaleString(`fa-IR`),i=`https://imenpishgaman.ir`,a={create:`ایجاد`,update:`ویرایش`,delete:`حذف`,upload:`آپلود`,login:`ورود`,publish:`انتشار`,unpublish:`عدم انتشار`,duplicate:`کپی`,reorder:`مرتبسازی`,"change-password":`تغییر رمز`},o={product:`محصول`,category:`دسته`,section:`بخش سایت`,media:`رسانه`,user:`کاربر`,message:`پیام`},s=(e,t)=>`${a[e]??e} ${o[t]??t}`;export{t as a,n as i,s as n,r,i as t};
|
||||
+473
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
var e=`/logo.png`;export{e as t};
|
||||
+1
@@ -0,0 +1 @@
|
||||
var e=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,80);export{e as t};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='#FF5A00'/><path d='M50 20 L72 28 V48 C72 66 62 76 50 82 C38 76 28 66 28 48 V28 Z' fill='#0A0A0C'/><path d='M40 50 L47 57 L62 42' stroke='#FF5A00' stroke-width='7' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>
|
||||
|
After Width: | Height: | Size: 337 B |
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>پنل مدیریت ایمن پیشگامان</title>
|
||||
<script type="module" crossorigin src="/assets/index-BuGK6ptM.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DjFpBvci.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
@@ -75,3 +75,10 @@ export interface SectionResponse<K extends SectionKey = SectionKey> {
|
||||
data: SiteSections[K];
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface AdminSectionSummary {
|
||||
key: SectionKey;
|
||||
updatedAt: string | null;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,4 +46,31 @@ defineProps<{ lockBody?: boolean }>();
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.ap {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ap__head {
|
||||
position: sticky;
|
||||
top: 52px;
|
||||
z-index: 20;
|
||||
padding: 10px 12px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.03);
|
||||
}
|
||||
|
||||
.ap__body {
|
||||
padding: 12px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ap__body--locked {
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { ICON_NAMES, type IconName } from "@imen/shared";
|
||||
import { computed } from "vue";
|
||||
import { ICONS } from "./icons";
|
||||
import { RECOMMENDED_ICONS } from "@imen/shared";
|
||||
import VuesaxIcon from "./VuesaxIcon.vue";
|
||||
|
||||
/** Picks one of the Lucide icons the website knows how to render */
|
||||
const value = defineModel<string>({ required: true });
|
||||
defineProps<{ allowClear?: boolean }>();
|
||||
|
||||
const options = ICON_NAMES.map((name) => ({ value: name, label: name }));
|
||||
const current = computed(() => ICONS[value.value as IconName]);
|
||||
const options = RECOMMENDED_ICONS.map((name) => ({ value: name, label: name }));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="flex size-8 shrink-0 items-center justify-center rounded-md border border-gray-200 text-[#ff5a00]">
|
||||
<component :is="current" v-if="current" class="size-4" />
|
||||
<span class="flex size-9 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-[#ff5a00]">
|
||||
<VuesaxIcon v-if="value" :name="value" :size="20" />
|
||||
<span v-else class="text-xs text-gray-400">---</span>
|
||||
</span>
|
||||
<a-select
|
||||
v-model:value="value"
|
||||
@@ -25,9 +23,9 @@ const current = computed(() => ICONS[value.value as IconName]);
|
||||
placeholder="انتخاب آیکون"
|
||||
>
|
||||
<template #option="{ value: name }">
|
||||
<span class="flex items-center gap-2" dir="ltr">
|
||||
<component :is="ICONS[name as IconName]" class="size-4" />
|
||||
{{ name }}
|
||||
<span class="flex items-center gap-2.5 py-0.5" dir="ltr">
|
||||
<VuesaxIcon :name="name" :size="18" class="text-orange-600" />
|
||||
<span class="text-xs font-mono">{{ name }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-select>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
|
||||
defineProps<{ title: string; subtitle?: string; back?: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<div class="page-header__main">
|
||||
<RouterLink v-if="back" :to="back" class="page-header__back" title="بازگشت">→</RouterLink>
|
||||
<RouterLink v-if="back" :to="back" class="page-header__back" title="بازگشت">
|
||||
<VuesaxIcon name="arrow-right" :size="18" />
|
||||
</RouterLink>
|
||||
<div class="min-w-0">
|
||||
<h1 class="page-header__title">{{ title }}</h1>
|
||||
<p v-if="subtitle" class="page-header__subtitle">{{ subtitle }}</p>
|
||||
@@ -19,42 +23,61 @@ defineProps<{ title: string; subtitle?: string; back?: string }>();
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
flex: 0 0 auto !important;
|
||||
flex-shrink: 0 !important;
|
||||
flex-grow: 0 !important;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-header__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.page-header__back {
|
||||
color: #94a3b8;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
transition: all 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page-header__back:hover {
|
||||
color: #334155;
|
||||
background: #e2e8f0;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.page-header__title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.page-header__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header__subtitle {
|
||||
margin: 2px 0 0;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.page-header__actions {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { resolveVuesaxIcon } from "@imen/shared";
|
||||
import icons from "@/icons/vuesax-linear.json";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** Short name (`add`) or full key (`vuesax-linear:add`) */
|
||||
/** Short name (`add`), alias (`ShieldCheck`, `Phone`), or full key (`vuesax-linear:add`) */
|
||||
name: string;
|
||||
size?: number | string;
|
||||
}>(),
|
||||
@@ -14,10 +15,9 @@ const props = withDefaults(
|
||||
const pack = icons as Record<string, string>;
|
||||
|
||||
const svgHtml = computed(() => {
|
||||
const key = props.name.startsWith("vuesax-linear:")
|
||||
? props.name
|
||||
: `vuesax-linear:${props.name}`;
|
||||
const raw = pack[key];
|
||||
const resolved = resolveVuesaxIcon(props.name || "");
|
||||
const key = resolved.includes(":") ? resolved : `vuesax-linear:${resolved}`;
|
||||
const raw = pack[key] || pack[`vuesax-linear:${resolved.toLowerCase()}`] || pack["vuesax-linear:shield-tick"];
|
||||
if (!raw) return "";
|
||||
return raw
|
||||
.replace(/\swidth="24"/, "")
|
||||
@@ -33,7 +33,7 @@ const box = computed(() =>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="vx-icon"
|
||||
class="anticon vx-icon"
|
||||
:style="{ width: box, height: box }"
|
||||
:aria-hidden="true"
|
||||
v-html="svgHtml"
|
||||
@@ -48,6 +48,7 @@ const box = computed(() =>
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
color: inherit;
|
||||
vertical-align: -0.15em;
|
||||
}
|
||||
|
||||
.vx-icon :deep(svg) {
|
||||
|
||||
@@ -1,104 +1,2 @@
|
||||
import type { IconName } from "@imen/shared";
|
||||
import {
|
||||
Award,
|
||||
BadgeCheck,
|
||||
Building2,
|
||||
ClipboardCheck,
|
||||
Clock,
|
||||
Construction,
|
||||
Ear,
|
||||
Factory,
|
||||
FileCheck,
|
||||
FileSearch,
|
||||
Flame,
|
||||
Footprints,
|
||||
Fuel,
|
||||
Gauge,
|
||||
Glasses,
|
||||
Globe,
|
||||
GraduationCap,
|
||||
Hand,
|
||||
Handshake,
|
||||
HardHat,
|
||||
Headset,
|
||||
HeartPulse,
|
||||
Leaf,
|
||||
Lock,
|
||||
Mail,
|
||||
MapPin,
|
||||
Medal,
|
||||
Microscope,
|
||||
Package,
|
||||
PackageCheck,
|
||||
Phone,
|
||||
Pickaxe,
|
||||
ShieldAlert,
|
||||
ShieldCheck,
|
||||
ShieldPlus,
|
||||
Shirt,
|
||||
Siren,
|
||||
Star,
|
||||
Target,
|
||||
ThumbsUp,
|
||||
TrafficCone,
|
||||
TriangleAlert,
|
||||
Truck,
|
||||
Users,
|
||||
Warehouse,
|
||||
Wind,
|
||||
Wrench,
|
||||
Zap,
|
||||
} from "lucide-vue-next";
|
||||
import type { Component } from "vue";
|
||||
|
||||
/** Same icon set as the website's DynamicIcon (ICON_NAMES in @imen/shared) */
|
||||
export const ICONS: Record<IconName, Component> = {
|
||||
ShieldCheck,
|
||||
ShieldPlus,
|
||||
ShieldAlert,
|
||||
BadgeCheck,
|
||||
Award,
|
||||
Medal,
|
||||
PackageCheck,
|
||||
Package,
|
||||
Truck,
|
||||
Warehouse,
|
||||
Globe,
|
||||
Users,
|
||||
Handshake,
|
||||
Headset,
|
||||
ClipboardCheck,
|
||||
FileCheck,
|
||||
FileSearch,
|
||||
GraduationCap,
|
||||
Gauge,
|
||||
Microscope,
|
||||
Wrench,
|
||||
HardHat,
|
||||
Shirt,
|
||||
Footprints,
|
||||
Hand,
|
||||
Glasses,
|
||||
Ear,
|
||||
Wind,
|
||||
TriangleAlert,
|
||||
Siren,
|
||||
TrafficCone,
|
||||
Construction,
|
||||
Factory,
|
||||
Building2,
|
||||
Fuel,
|
||||
Pickaxe,
|
||||
Flame,
|
||||
Zap,
|
||||
Leaf,
|
||||
HeartPulse,
|
||||
Clock,
|
||||
Phone,
|
||||
Mail,
|
||||
MapPin,
|
||||
Star,
|
||||
ThumbsUp,
|
||||
Target,
|
||||
Lock,
|
||||
};
|
||||
export { ICON_ALIASES, ICON_NAMES, RECOMMENDED_ICONS, resolveVuesaxIcon } from "@imen/shared";
|
||||
export type { IconName } from "@imen/shared";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ROLE_LABELS } from "@imen/shared";
|
||||
import { computed, h, ref, watch } from "vue";
|
||||
import { computed, h, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { api } from "@/api/http";
|
||||
import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { SITE_URL } from "@/utils/format";
|
||||
@@ -10,13 +11,28 @@ const auth = useAuthStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const drawer = ref(false);
|
||||
const unreadCount = ref(0);
|
||||
|
||||
async function fetchUnread() {
|
||||
try {
|
||||
const res = await api<{ total: number }>("/admin/messages", {
|
||||
query: { status: "NEW", pageSize: 1 },
|
||||
});
|
||||
unreadCount.value = res.total;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchUnread);
|
||||
watch(() => route.fullPath, fetchUnread);
|
||||
|
||||
/** Icon badge box + Vuesax glyph (keeps pre-swap menu structure) */
|
||||
const menuIcon = (name: string) => () =>
|
||||
h("span", { class: "admin-menu__icon" }, [h(VuesaxIcon, { name, size: 16 })]);
|
||||
h("span", { class: "admin-menu__icon" }, [h(VuesaxIcon, { name, size: 20 })]);
|
||||
|
||||
const items = computed(() => [
|
||||
{ key: "dashboard", icon: menuIcon("element-3"), label: "داشبورد" },
|
||||
{ key: "dashboard", icon: menuIcon("chart-square"), label: "داشبورد" },
|
||||
{ key: "products", icon: menuIcon("bag-2"), label: "محصولات" },
|
||||
{ key: "categories", icon: menuIcon("category"), label: "دستهبندیها" },
|
||||
{ key: "sections", icon: menuIcon("row-vertical"), label: "بخشهای سایت" },
|
||||
@@ -58,16 +74,35 @@ const selected = computed(() => {
|
||||
const pageTitle = computed(() => PAGE_TITLES[String(route.name ?? "")] ?? "پنل مدیریت");
|
||||
const userInitial = computed(() => (auth.user?.name?.trim().charAt(0) || "ک").slice(0, 1));
|
||||
|
||||
const isChildRoute = computed(() => {
|
||||
const name = String(route.name ?? "");
|
||||
return ["product-new", "product-edit", "category-new", "category-edit", "section-edit"].includes(name);
|
||||
});
|
||||
|
||||
const parentBack = computed(() => {
|
||||
const name = String(route.name ?? "");
|
||||
if (name.startsWith("product")) return { name: "products", label: "محصولات" };
|
||||
if (name.startsWith("category")) return { name: "categories", label: "دستهبندیها" };
|
||||
if (name.startsWith("section")) return { name: "sections", label: "بخشهای سایت" };
|
||||
return { name: "dashboard", label: "داشبورد" };
|
||||
});
|
||||
|
||||
const go = ({ key }: { key: string | number }) => {
|
||||
drawer.value = false;
|
||||
void router.push({ name: String(key) });
|
||||
};
|
||||
|
||||
const navigateTo = (name: string) => {
|
||||
drawer.value = false;
|
||||
void router.push({ name });
|
||||
};
|
||||
|
||||
watch(() => route.fullPath, () => (drawer.value = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-layout class="admin-shell">
|
||||
<!-- Desktop Sider (lg and up) -->
|
||||
<a-layout-sider
|
||||
:width="248"
|
||||
breakpoint="lg"
|
||||
@@ -97,42 +132,250 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
|
||||
<!-- Mobile App Hub Drawer (Rich App Grid) -->
|
||||
<a-drawer
|
||||
v-model:open="drawer"
|
||||
placement="right"
|
||||
:width="280"
|
||||
:width="320"
|
||||
:closable="false"
|
||||
class="admin-drawer"
|
||||
:body-style="{ padding: 0, background: '#ffffff' }"
|
||||
class="mobile-hub-drawer"
|
||||
:body-style="{ padding: 0, background: '#f8fafc' }"
|
||||
>
|
||||
<div class="admin-sider__brand">
|
||||
<span class="admin-sider__logo">
|
||||
<img src="/logo.png" alt="" class="size-7 object-contain" />
|
||||
</span>
|
||||
<div class="leading-tight">
|
||||
<div class="text-[14px] font-bold text-[#0f172a]">ایمن پیشگامان</div>
|
||||
<div class="text-[10px] font-medium text-[#94a3b8]">پنل مدیریت</div>
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Hub User Profile Header -->
|
||||
<div class="p-4 bg-white border-b border-slate-200/80">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span class="size-8 rounded-lg bg-orange-50 border border-orange-100 flex items-center justify-center">
|
||||
<img src="/logo.png" alt="" class="size-5 object-contain" />
|
||||
</span>
|
||||
<div>
|
||||
<div class="text-sm font-bold text-slate-800">ایمن پیشگامان</div>
|
||||
<div class="text-[10px] text-slate-400">اپلیکیشن مدیریت</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-full flex items-center justify-center text-slate-400 hover:text-slate-600 hover:bg-slate-100 transition"
|
||||
@click="drawer = false"
|
||||
>
|
||||
<VuesaxIcon name="close-circle" :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-3 p-2.5 rounded-xl bg-slate-50 border border-slate-100 cursor-pointer active:scale-[0.99] transition"
|
||||
@click="navigateTo('profile')"
|
||||
>
|
||||
<span class="size-10 rounded-full bg-linear-to-br from-[#ff5a00] to-[#ff8a3d] text-white flex items-center justify-center font-bold text-sm shadow-xs">
|
||||
{{ userInitial }}
|
||||
</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-xs font-bold text-slate-800 truncate">{{ auth.user?.name }}</div>
|
||||
<div class="flex items-center gap-2 mt-0.5">
|
||||
<span class="text-[10px] text-orange-600 font-medium">
|
||||
{{ auth.user && ROLE_LABELS[auth.user.role] }}
|
||||
</span>
|
||||
<span class="text-[10px] text-slate-400 font-mono" dir="ltr">{{ auth.user?.phone }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<VuesaxIcon name="arrow-left" :size="14" class="text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hub Apps Grid Content -->
|
||||
<div class="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
<div>
|
||||
<div class="text-[11px] font-bold text-slate-400 mb-2.5 px-1">بخشها و کاتالوگ</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center justify-center p-3 rounded-xl bg-white border border-slate-200/70 shadow-2xs hover:border-orange-200 active:bg-orange-50/40 transition"
|
||||
:class="{ '!border-orange-500 !bg-orange-50/30 font-bold text-orange-600': route.name === 'dashboard' }"
|
||||
@click="navigateTo('dashboard')"
|
||||
>
|
||||
<div class="size-9 rounded-lg bg-orange-50 text-orange-600 flex items-center justify-center mb-1.5">
|
||||
<VuesaxIcon name="chart-square" :size="18" />
|
||||
</div>
|
||||
<span class="text-xs text-slate-700">داشبورد</span>
|
||||
<span class="text-[9px] text-slate-400 mt-0.5">نمای کلی آمار</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center justify-center p-3 rounded-xl bg-white border border-slate-200/70 shadow-2xs hover:border-orange-200 active:bg-orange-50/40 transition"
|
||||
:class="{ '!border-orange-500 !bg-orange-50/30 font-bold text-orange-600': selected.includes('products') }"
|
||||
@click="navigateTo('products')"
|
||||
>
|
||||
<div class="size-9 rounded-lg bg-orange-50 text-orange-600 flex items-center justify-center mb-1.5">
|
||||
<VuesaxIcon name="bag-2" :size="18" />
|
||||
</div>
|
||||
<span class="text-xs text-slate-700">محصولات</span>
|
||||
<span class="text-[9px] text-slate-400 mt-0.5">کاتالوگ و موجودی</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center justify-center p-3 rounded-xl bg-white border border-slate-200/70 shadow-2xs hover:border-blue-200 active:bg-blue-50/40 transition"
|
||||
:class="{ '!border-blue-500 !bg-blue-50/30 font-bold text-blue-600': selected.includes('categories') }"
|
||||
@click="navigateTo('categories')"
|
||||
>
|
||||
<div class="size-9 rounded-lg bg-blue-50 text-blue-600 flex items-center justify-center mb-1.5">
|
||||
<VuesaxIcon name="category" :size="18" />
|
||||
</div>
|
||||
<span class="text-xs text-slate-700">دستهبندیها</span>
|
||||
<span class="text-[9px] text-slate-400 mt-0.5">گروههای کالا</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center justify-center p-3 rounded-xl bg-white border border-slate-200/70 shadow-2xs hover:border-amber-200 active:bg-amber-50/40 transition relative"
|
||||
:class="{ '!border-amber-500 !bg-amber-50/30 font-bold text-amber-600': route.name === 'messages' }"
|
||||
@click="navigateTo('messages')"
|
||||
>
|
||||
<span v-if="unreadCount > 0" class="absolute top-2 end-2 size-2.5 rounded-full bg-orange-600 ring-2 ring-white animate-pulse" />
|
||||
<div class="size-9 rounded-lg bg-amber-50 text-amber-600 flex items-center justify-center mb-1.5">
|
||||
<VuesaxIcon name="sms" :size="18" />
|
||||
</div>
|
||||
<span class="text-xs text-slate-700">پیامهای تماس</span>
|
||||
<span class="text-[9px] text-slate-400 mt-0.5">درخواستهای مشاوره</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-[11px] font-bold text-slate-400 mb-2.5 px-1">محتوا و وبسایت</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center justify-center p-3 rounded-xl bg-white border border-slate-200/70 shadow-2xs hover:border-purple-200 active:bg-purple-50/40 transition"
|
||||
:class="{ '!border-purple-500 !bg-purple-50/30 font-bold text-purple-600': route.name === 'media' }"
|
||||
@click="navigateTo('media')"
|
||||
>
|
||||
<div class="size-9 rounded-lg bg-purple-50 text-purple-600 flex items-center justify-center mb-1.5">
|
||||
<VuesaxIcon name="gallery" :size="18" />
|
||||
</div>
|
||||
<span class="text-xs text-slate-700">کتابخانه رسانه</span>
|
||||
<span class="text-[9px] text-slate-400 mt-0.5">تصاویر WebP</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center justify-center p-3 rounded-xl bg-white border border-slate-200/70 shadow-2xs hover:border-emerald-200 active:bg-emerald-50/40 transition"
|
||||
:class="{ '!border-emerald-500 !bg-emerald-50/30 font-bold text-emerald-600': selected.includes('sections') }"
|
||||
@click="navigateTo('sections')"
|
||||
>
|
||||
<div class="size-9 rounded-lg bg-emerald-50 text-emerald-600 flex items-center justify-center mb-1.5">
|
||||
<VuesaxIcon name="row-vertical" :size="18" />
|
||||
</div>
|
||||
<span class="text-xs text-slate-700">بخشهای سایت</span>
|
||||
<span class="text-[9px] text-slate-400 mt-0.5">تنظیمات صفحه اصلی</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="auth.isAdmin">
|
||||
<div class="text-[11px] font-bold text-slate-400 mb-2.5 px-1">مدیریت سیستم</div>
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-3 p-3 rounded-xl bg-white border border-slate-200/70 shadow-2xs hover:border-slate-300 active:bg-slate-50 transition text-right"
|
||||
:class="{ '!border-orange-500 font-bold': route.name === 'users' }"
|
||||
@click="navigateTo('users')"
|
||||
>
|
||||
<div class="size-9 rounded-lg bg-slate-100 text-slate-600 flex items-center justify-center shrink-0">
|
||||
<VuesaxIcon name="profile-2user" :size="18" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-xs font-bold text-slate-800">مدیریت کاربران پنل</div>
|
||||
<div class="text-[10px] text-slate-400">حسابها و سطح دسترسیها</div>
|
||||
</div>
|
||||
<VuesaxIcon name="arrow-left" :size="14" class="text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hub Footer System Actions -->
|
||||
<div class="p-3 bg-white border-t border-slate-200/80 space-y-2">
|
||||
<a
|
||||
:href="SITE_URL"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="flex items-center justify-between p-2.5 rounded-lg text-slate-600 hover:bg-slate-50 text-xs font-semibold transition"
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<VuesaxIcon name="export" :size="16" class="text-slate-400" />
|
||||
مشاهده وبسایت اصلی
|
||||
</span>
|
||||
<VuesaxIcon name="arrow-left" :size="12" class="text-slate-300" />
|
||||
</a>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="w-full flex items-center justify-center gap-2 p-2.5 rounded-lg text-rose-600 hover:bg-rose-50 text-xs font-semibold transition"
|
||||
@click="auth.logout()"
|
||||
>
|
||||
<VuesaxIcon name="logout" :size="16" />
|
||||
خروج از حساب کاربری
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-sider__nav">
|
||||
<div class="admin-sider__label">منوی اصلی</div>
|
||||
<a-menu
|
||||
class="admin-menu"
|
||||
mode="inline"
|
||||
:selected-keys="selected"
|
||||
:items="items"
|
||||
@click="go"
|
||||
/>
|
||||
</div>
|
||||
</a-drawer>
|
||||
|
||||
<a-layout>
|
||||
<a-layout-header class="admin-header">
|
||||
<div class="admin-header__start">
|
||||
<a-button class="admin-header__menu-btn lg:!hidden" @click="drawer = true">
|
||||
<template #icon><VuesaxIcon name="menu" :size="18" /></template>
|
||||
</a-button>
|
||||
<!-- Dedicated Mobile App Bar (Mobile Only) -->
|
||||
<header class="mobile-app-bar lg:!hidden">
|
||||
<div v-if="isChildRoute" class="flex items-center justify-between w-full">
|
||||
<button
|
||||
type="button"
|
||||
class="mobile-app-bar__back-btn"
|
||||
@click="router.push(parentBack)"
|
||||
>
|
||||
<VuesaxIcon name="arrow-right" :size="20" />
|
||||
<span class="text-xs font-bold text-slate-700">بازگشت به {{ parentBack.label }}</span>
|
||||
</button>
|
||||
<div class="text-xs font-bold text-slate-800 truncate max-w-[140px]">
|
||||
{{ pageTitle }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span class="size-8 rounded-lg bg-orange-50 border border-orange-100 flex items-center justify-center">
|
||||
<img src="/logo.png" alt="" class="size-5 object-contain" />
|
||||
</span>
|
||||
<div class="leading-none">
|
||||
<div class="text-xs font-extrabold text-slate-800">ایمن پیشگامان</div>
|
||||
<div class="text-[10px] font-medium text-orange-600 mt-0.5">{{ pageTitle }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<a
|
||||
:href="SITE_URL"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-500 hover:text-orange-600 hover:bg-orange-50 transition"
|
||||
title="مشاهده سایت"
|
||||
>
|
||||
<VuesaxIcon name="export" :size="18" />
|
||||
</a>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg bg-linear-to-br from-[#ff5a00] to-[#ff8a3d] text-white flex items-center justify-center font-bold text-xs shadow-xs"
|
||||
@click="router.push({ name: 'profile' })"
|
||||
>
|
||||
{{ userInitial }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Desktop Header (Desktop Only) -->
|
||||
<a-layout-header class="admin-header !hidden lg:!flex">
|
||||
<div class="admin-header__start">
|
||||
<div class="admin-header__crumb">
|
||||
<span class="admin-header__crumb-kicker">پنل مدیریت</span>
|
||||
<span class="admin-header__crumb-sep" aria-hidden="true">/</span>
|
||||
@@ -179,13 +422,69 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
</div>
|
||||
</a-layout-header>
|
||||
|
||||
<a-layout-content class="admin-content">
|
||||
<!-- Content Viewport -->
|
||||
<a-layout-content class="admin-content" :class="{ 'admin-content--has-bottom-nav': !isChildRoute }">
|
||||
<div class="admin-page">
|
||||
<RouterView v-slot="{ Component, route: r }">
|
||||
<component :is="Component" :key="r.path" />
|
||||
</RouterView>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
|
||||
<!-- Purpose-Built Mobile Bottom Navigation Bar -->
|
||||
<nav v-if="!isChildRoute" class="mobile-nav-bar lg:!hidden">
|
||||
<button
|
||||
type="button"
|
||||
class="mobile-nav-item"
|
||||
:class="{ 'mobile-nav-item--active': route.name === 'dashboard' }"
|
||||
@click="navigateTo('dashboard')"
|
||||
>
|
||||
<div class="mobile-nav-item__icon">
|
||||
<VuesaxIcon name="chart-square" :size="20" />
|
||||
</div>
|
||||
<span class="mobile-nav-item__label">داشبورد</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mobile-nav-item"
|
||||
:class="{ 'mobile-nav-item--active': selected.includes('products') }"
|
||||
@click="navigateTo('products')"
|
||||
>
|
||||
<div class="mobile-nav-item__icon">
|
||||
<VuesaxIcon name="bag-2" :size="20" />
|
||||
</div>
|
||||
<span class="mobile-nav-item__label">محصولات</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mobile-nav-item"
|
||||
:class="{ 'mobile-nav-item--active': route.name === 'messages' }"
|
||||
@click="navigateTo('messages')"
|
||||
>
|
||||
<div class="mobile-nav-item__icon relative">
|
||||
<VuesaxIcon name="sms" :size="20" />
|
||||
<span
|
||||
v-if="unreadCount > 0"
|
||||
class="absolute -top-1 -end-1 size-2 rounded-full bg-orange-600 ring-2 ring-white"
|
||||
/>
|
||||
</div>
|
||||
<span class="mobile-nav-item__label">پیامها</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mobile-nav-item"
|
||||
:class="{ 'mobile-nav-item--active': drawer || ['categories', 'sections', 'media', 'users', 'profile'].includes(String(route.name ?? '')) }"
|
||||
@click="drawer = true"
|
||||
>
|
||||
<div class="mobile-nav-item__icon">
|
||||
<VuesaxIcon name="category" :size="20" />
|
||||
</div>
|
||||
<span class="mobile-nav-item__label">ابزارها</span>
|
||||
</button>
|
||||
</nav>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
@@ -247,14 +546,22 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
|
||||
}
|
||||
|
||||
.admin-page > :deep(*) {
|
||||
.admin-page > :deep(.page-header) {
|
||||
flex: 0 0 auto !important;
|
||||
overflow: visible !important;
|
||||
padding: 16px 20px 14px !important;
|
||||
border-bottom: 1px solid #eef1f6;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.admin-page > :deep(*:not(.page-header)) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Pages that don't use AdminPage still scroll inside the panel */
|
||||
.admin-page > :deep(*:not(.ap)) {
|
||||
.admin-page > :deep(*:not(.ap):not(.page-header)) {
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
@@ -276,8 +583,9 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
|
||||
.admin-header__menu-btn {
|
||||
border-radius: 10px !important;
|
||||
border-color: #e2e8f0 !important;
|
||||
border: none !important;
|
||||
color: #475569 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.admin-header__crumb {
|
||||
@@ -325,26 +633,21 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: 36px;
|
||||
padding-inline: 14px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
padding-inline: 8px;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
color: #475569;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
color 0.18s ease,
|
||||
border-color 0.18s ease,
|
||||
background 0.18s ease,
|
||||
box-shadow 0.18s ease;
|
||||
transition: color 0.18s ease;
|
||||
}
|
||||
|
||||
.admin-header__site:hover {
|
||||
color: #e04f00;
|
||||
border-color: #ffd0b5;
|
||||
background: #fff7f2;
|
||||
box-shadow: 0 2px 8px rgba(255, 90, 0, 0.08);
|
||||
color: #ff5a00;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.admin-header__divider {
|
||||
@@ -362,7 +665,7 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
padding-block: 4px;
|
||||
padding-inline: 4px 10px;
|
||||
border: 1px solid #e8ecf2;
|
||||
border-radius: 999px;
|
||||
border-radius: var(--ant-border-radius, 8px);
|
||||
background: #f8fafc;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
@@ -385,7 +688,7 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 999px;
|
||||
border-radius: calc(var(--ant-border-radius, 8px) - 2px);
|
||||
background: linear-gradient(135deg, #ff5a00, #ff8a3d);
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
@@ -496,16 +799,16 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
}
|
||||
|
||||
.admin-menu :deep(.ant-menu-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 44px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
height: 48px !important;
|
||||
margin: 0 0 6px !important;
|
||||
padding-inline: 12px !important;
|
||||
padding-inline: 10px 14px !important;
|
||||
border: 1px solid transparent !important;
|
||||
border-radius: 12px !important;
|
||||
color: #475569 !important;
|
||||
font-weight: 500;
|
||||
line-height: 44px !important;
|
||||
line-height: 1.5 !important;
|
||||
transition:
|
||||
background 0.2s ease,
|
||||
color 0.2s ease,
|
||||
@@ -513,41 +816,84 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.ant-menu-item-icon),
|
||||
.admin-menu :deep(.ant-menu-item .anticon) {
|
||||
.admin-menu :deep(.ant-menu-item-icon) {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
margin-inline-end: 0 !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
width: 36px !important;
|
||||
height: 36px !important;
|
||||
min-width: 36px !important;
|
||||
min-height: 36px !important;
|
||||
margin: 0 !important;
|
||||
margin-inline-end: 12px !important;
|
||||
padding: 0 !important;
|
||||
font-size: 0 !important;
|
||||
line-height: 0 !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.admin-menu__icon) {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-inline-end: 10px;
|
||||
border-radius: 9px;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
width: 36px !important;
|
||||
height: 36px !important;
|
||||
min-width: 36px !important;
|
||||
min-height: 36px !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
flex-shrink: 0 !important;
|
||||
border-radius: 10px !important;
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
line-height: 0 !important;
|
||||
transition:
|
||||
background 0.2s ease,
|
||||
color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.admin-menu__icon .vx-icon) {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
.admin-menu :deep(.admin-menu__icon .vx-icon),
|
||||
.admin-menu :deep(.admin-menu__icon .anticon) {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
min-width: 20px !important;
|
||||
min-height: 20px !important;
|
||||
max-width: 20px !important;
|
||||
max-height: 20px !important;
|
||||
margin: 0 !important;
|
||||
margin-inline-end: 0 !important;
|
||||
margin-inline-start: 0 !important;
|
||||
padding: 0 !important;
|
||||
line-height: 0 !important;
|
||||
vertical-align: 0 !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
color: inherit;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.admin-menu__icon svg) {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
max-width: 20px !important;
|
||||
max-height: 20px !important;
|
||||
display: block !important;
|
||||
margin: 0 auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.ant-menu-title-content) {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
padding-inline-start: 8px !important;
|
||||
padding-right: 8px !important;
|
||||
margin-inline-start: 0 !important;
|
||||
line-height: 1.5 !important;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.ant-menu-item:hover) {
|
||||
@@ -556,9 +902,9 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
border-color: #eef1f6 !important;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.ant-menu-item:hover .admin-menu__icon) {
|
||||
background: #e8eef7;
|
||||
color: #475569;
|
||||
.admin-menu :deep(.ant-menu-item:hover:not(.ant-menu-item-selected) .admin-menu__icon) {
|
||||
background: #e8eef7 !important;
|
||||
color: #475569 !important;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.ant-menu-item-selected) {
|
||||
@@ -572,10 +918,133 @@ watch(() => route.fullPath, () => (drawer.value = false));
|
||||
.admin-menu :deep(.ant-menu-item-selected .admin-menu__icon) {
|
||||
background: #ff5a00 !important;
|
||||
color: #ffffff !important;
|
||||
box-shadow: 0 4px 10px rgba(255, 90, 0, 0.32);
|
||||
box-shadow: 0 4px 12px rgba(255, 90, 0, 0.35) !important;
|
||||
}
|
||||
|
||||
.admin-menu :deep(.ant-menu-item-selected::after) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------
|
||||
* Mobile App Experience Styles (Viewport < 1024px)
|
||||
* ------------------------------------------------------------- */
|
||||
.mobile-app-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 52px;
|
||||
padding-inline: 14px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid #eef1f6;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.02);
|
||||
}
|
||||
|
||||
.mobile-app-bar__back-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 8px;
|
||||
margin-inline-start: -6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #334155;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.mobile-app-bar__back-btn:active {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
.mobile-nav-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 40;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 60px;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
backdrop-filter: blur(16px);
|
||||
border-top: 1px solid #e2e8f0;
|
||||
box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.mobile-nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding-block: 4px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s ease;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.mobile-nav-item__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 24px;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.mobile-nav-item__label {
|
||||
margin-top: 2px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.mobile-nav-item--active {
|
||||
color: #ff5a00;
|
||||
}
|
||||
|
||||
.mobile-nav-item--active .mobile-nav-item__icon {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.admin-content {
|
||||
padding: 0 !important;
|
||||
background: #f8fafc !important;
|
||||
overflow-y: auto !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.admin-content--has-bottom-nav {
|
||||
padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
}
|
||||
|
||||
.admin-page {
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.admin-page > :deep(.page-header) {
|
||||
padding: 12px 14px !important;
|
||||
border-bottom: 1px solid #eef1f6 !important;
|
||||
background: #ffffff !important;
|
||||
}
|
||||
|
||||
.admin-page > :deep(*:not(.ap):not(.page-header)) {
|
||||
padding: 12px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,8 +5,10 @@ import { api, errorMessage } from "@/api/http";
|
||||
import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import type { AdminCategory } from "@/api/types";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { faNumber, SITE_URL } from "@/utils/format";
|
||||
|
||||
const auth = useAuthStore();
|
||||
const rows = ref<AdminCategory[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -67,7 +69,7 @@ onMounted(load);
|
||||
</RouterLink>
|
||||
</PageHeader>
|
||||
|
||||
<a-card :body-style="{ padding: 0 }">
|
||||
<a-card class="!hidden lg:!block" :body-style="{ padding: 0 }">
|
||||
<a-table :columns="columns" :data-source="rows" :loading="loading" row-key="id" :pagination="false" :scroll="{ x: 800 }">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'cover'">
|
||||
@@ -108,6 +110,7 @@ onMounted(load);
|
||||
<a-button size="small" title="مشاهده در سایت"><template #icon><VuesaxIcon name="export" :size="16" /></template></a-button>
|
||||
</a>
|
||||
<a-popconfirm
|
||||
v-if="auth.isAdmin"
|
||||
title="این دسته حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@@ -122,4 +125,132 @@ onMounted(load);
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- Mobile Category Cards View (Mobile Only) -->
|
||||
<div class="mobile-categories-view block lg:!hidden pb-24">
|
||||
<a-spin :spinning="loading">
|
||||
<a-empty v-if="!rows.length && !loading" description="دستهبندی ثبت نشده است" class="py-12" />
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="(record, index) in rows"
|
||||
:key="record.id"
|
||||
class="bg-white rounded-xl border border-slate-200/80 p-3.5 shadow-2xs transition"
|
||||
>
|
||||
<!-- Top Row: Cover + Info -->
|
||||
<div class="flex items-start gap-3">
|
||||
<img
|
||||
v-if="record.coverImage"
|
||||
:src="record.coverImage.url"
|
||||
alt=""
|
||||
class="size-14 rounded-lg object-cover shrink-0 border border-slate-100 bg-slate-50"
|
||||
/>
|
||||
<div v-else class="size-14 rounded-lg bg-slate-100 shrink-0 flex items-center justify-center text-slate-300">
|
||||
<VuesaxIcon name="category" :size="22" />
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<RouterLink
|
||||
:to="{ name: 'category-edit', params: { id: record.id } }"
|
||||
class="font-bold text-[14px] text-slate-800 hover:text-orange-600 line-clamp-1"
|
||||
>
|
||||
{{ record.name }}
|
||||
</RouterLink>
|
||||
<span class="text-[10px] font-mono text-slate-500 bg-slate-100 px-1.5 py-0.5 rounded shrink-0" dir="ltr">
|
||||
{{ record.code }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 mt-1.5 text-xs">
|
||||
<RouterLink
|
||||
:to="{ name: 'products', query: { categoryId: record.id } }"
|
||||
class="inline-flex items-center gap-1 text-orange-600 font-bold bg-orange-50 px-2 py-0.5 rounded-full"
|
||||
>
|
||||
{{ faNumber(record.productCount) }} محصول
|
||||
</RouterLink>
|
||||
|
||||
<a-tag :color="record.isPublished ? 'green' : 'default'" class="!m-0 !text-[10px]">
|
||||
{{ record.isPublished ? "منتشرشده" : "مخفی" }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Row: Reorder Controls + Actions -->
|
||||
<div class="flex items-center justify-between border-t border-slate-100 pt-2.5 mt-3">
|
||||
<!-- Touch Reorder Buttons -->
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-[11px] text-slate-400 me-1">ترتیب:</span>
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-600 bg-slate-100 hover:bg-slate-200 disabled:opacity-30 disabled:pointer-events-none active:bg-slate-300 transition"
|
||||
:disabled="index === 0"
|
||||
@click="move(index, -1)"
|
||||
>
|
||||
<VuesaxIcon name="arrow-up" :size="16" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-600 bg-slate-100 hover:bg-slate-200 disabled:opacity-30 disabled:pointer-events-none active:bg-slate-300 transition"
|
||||
:disabled="index === rows.length - 1"
|
||||
@click="move(index, 1)"
|
||||
>
|
||||
<VuesaxIcon name="arrow-down" :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex items-center gap-1.5">
|
||||
<RouterLink :to="{ name: 'category-edit', params: { id: record.id } }">
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-600 bg-slate-100 hover:bg-slate-200 active:bg-slate-300 transition"
|
||||
title="ویرایش"
|
||||
>
|
||||
<VuesaxIcon name="edit-2" :size="16" />
|
||||
</button>
|
||||
</RouterLink>
|
||||
|
||||
<a :href="`${SITE_URL}/categories/${record.slug}`" target="_blank" rel="noopener">
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-600 bg-slate-100 hover:bg-slate-200 active:bg-slate-300 transition"
|
||||
title="مشاهده در سایت"
|
||||
>
|
||||
<VuesaxIcon name="export" :size="16" />
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<a-popconfirm
|
||||
v-if="auth.isAdmin"
|
||||
title="این دسته حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@confirm="remove(record as AdminCategory)"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-rose-500 bg-rose-50 hover:bg-rose-100 disabled:opacity-30 disabled:pointer-events-none active:bg-rose-200 transition"
|
||||
:disabled="record.productCount > 0"
|
||||
:title="record.productCount > 0 ? 'ابتدا محصولات این دسته را حذف کنید' : 'حذف'"
|
||||
>
|
||||
<VuesaxIcon name="trash" :size="16" />
|
||||
</button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
<!-- Mobile Floating Action Button (FAB) for New Category -->
|
||||
<RouterLink
|
||||
:to="{ name: 'category-new' }"
|
||||
class="fixed start-4 bottom-[72px] z-30 flex items-center gap-2 px-4 py-2.5 rounded-full bg-linear-to-r from-[#ff5a00] to-[#ff7a29] text-white font-bold text-xs shadow-lg shadow-orange-500/30 active:scale-95 transition"
|
||||
>
|
||||
<VuesaxIcon name="add" :size="18" />
|
||||
<span>دسته جدید</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -102,6 +102,8 @@ const groupTitle = (g: Group) => {
|
||||
return count ? `${g.name} (${count} محصول)` : g.name;
|
||||
};
|
||||
|
||||
const mobileTab = ref<"main" | "groups" | "media" | "seo">("main");
|
||||
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
@@ -113,11 +115,50 @@ onMounted(load);
|
||||
</a-button>
|
||||
</PageHeader>
|
||||
|
||||
<!-- Mobile Segmented Workflow Tabs -->
|
||||
<div class="lg:hidden mb-4 overflow-x-auto pb-1 scrollbar-none">
|
||||
<div class="flex items-center gap-1.5 min-w-max bg-gray-100 p-1 rounded-xl">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg px-3 py-1.5 text-xs font-bold transition-all"
|
||||
:class="mobileTab === 'main' ? 'bg-white text-orange-600 shadow-sm' : 'text-gray-600'"
|
||||
@click="mobileTab = 'main'"
|
||||
>
|
||||
اطلاعات اصلی
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg px-3 py-1.5 text-xs font-bold transition-all flex items-center gap-1"
|
||||
:class="mobileTab === 'groups' ? 'bg-white text-orange-600 shadow-sm' : 'text-gray-600'"
|
||||
@click="mobileTab = 'groups'"
|
||||
>
|
||||
<span>زیرگروهها</span>
|
||||
<span class="rounded-full bg-gray-200 px-1.5 py-0.2 text-[10px]">{{ model.groups.length }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg px-3 py-1.5 text-xs font-bold transition-all"
|
||||
:class="mobileTab === 'media' ? 'bg-white text-orange-600 shadow-sm' : 'text-gray-600'"
|
||||
@click="mobileTab = 'media'"
|
||||
>
|
||||
کاور و آیکون
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg px-3 py-1.5 text-xs font-bold transition-all"
|
||||
:class="mobileTab === 'seo' ? 'bg-white text-orange-600 shadow-sm' : 'text-gray-600'"
|
||||
@click="mobileTab = 'seo'"
|
||||
>
|
||||
سئو و انتشار
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-form layout="vertical" @submit.prevent="save">
|
||||
<a-form layout="vertical" class="pb-20 lg:pb-0" @submit.prevent="save">
|
||||
<div class="grid gap-6 xl:grid-cols-3">
|
||||
<div class="space-y-6 xl:col-span-2">
|
||||
<a-card title="اطلاعات اصلی">
|
||||
<a-card title="اطلاعات اصلی" :class="mobileTab !== 'main' && 'max-lg:hidden'">
|
||||
<div class="grid gap-x-4 md:grid-cols-2">
|
||||
<FormField path="name" label="نام دسته" required>
|
||||
<a-input v-model:value="model.name" />
|
||||
@@ -144,7 +185,7 @@ onMounted(load);
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card title="زیرگروهها" :body-style="{ background: '#fafafa' }">
|
||||
<a-card title="زیرگروهها" :body-style="{ background: '#fafafa' }" :class="mobileTab !== 'groups' && 'max-lg:hidden'">
|
||||
<p class="mb-4 text-sm text-gray-500">
|
||||
زیرگروهها در صفحه دسته بهصورت فیلتر نمایش داده میشوند. با حذف یک زیرگروه، محصولات آن بدون زیرگروه میمانند.
|
||||
</p>
|
||||
@@ -172,23 +213,23 @@ onMounted(load);
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<a-card title="انتشار">
|
||||
<a-card title="انتشار" :class="mobileTab !== 'seo' && 'max-lg:hidden'">
|
||||
<FormField path="isPublished">
|
||||
<a-switch v-model:checked="model.isPublished" />
|
||||
<span class="ms-2">{{ model.isPublished ? "در سایت نمایش داده میشود" : "مخفی" }}</span>
|
||||
</FormField>
|
||||
</a-card>
|
||||
<a-card title="تصویر کاور">
|
||||
<a-card title="تصویر کاور" :class="mobileTab !== 'media' && 'max-lg:hidden'">
|
||||
<FormField path="coverImageId">
|
||||
<ImageField :url="cover?.url" clearable @pick="pickCover" @clear="cover = null; model.coverImageId = null" />
|
||||
</FormField>
|
||||
</a-card>
|
||||
<a-card title="آیکون">
|
||||
<a-card title="آیکون" :class="mobileTab !== 'media' && 'max-lg:hidden'">
|
||||
<FormField path="icon">
|
||||
<IconSelect v-model="model.icon" allow-clear />
|
||||
</FormField>
|
||||
</a-card>
|
||||
<a-card title="سئو">
|
||||
<a-card title="سئو" :class="mobileTab !== 'seo' && 'max-lg:hidden'">
|
||||
<FormField path="seoTitle" label="عنوان صفحه" hint="خالی = نام دسته">
|
||||
<a-input v-model:value="model.seoTitle" :maxlength="120" show-count />
|
||||
</FormField>
|
||||
@@ -200,4 +241,28 @@ onMounted(load);
|
||||
</div>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
|
||||
<!-- Mobile Sticky Bottom Action Bar -->
|
||||
<div class="lg:hidden fixed bottom-0 inset-x-0 z-30 border-t border-gray-200 bg-white/95 px-4 py-2.5 backdrop-blur-md shadow-lg flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<a-switch v-model:checked="model.isPublished" size="small" />
|
||||
<span class="text-xs font-medium text-gray-700">{{ model.isPublished ? "انتشار" : "پیشنویس" }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
v-if="model.slug"
|
||||
:href="`/categories/${model.slug}`"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<a-button size="middle" class="!px-3" title="مشاهده در سایت">
|
||||
<template #icon><VuesaxIcon name="eye" :size="16" /></template>
|
||||
</a-button>
|
||||
</a>
|
||||
<a-button type="primary" size="middle" class="!font-bold !px-5" :loading="saving" @click="save">
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
ذخیره تغییرات
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,84 +1,338 @@
|
||||
<script setup lang="ts">
|
||||
import { type DashboardDto, MESSAGE_STATUS_LABELS } from "@imen/shared";
|
||||
import { message } from "ant-design-vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { api, errorMessage } from "@/api/http";
|
||||
import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { activityLabel, faNumber, jalali } from "@/utils/format";
|
||||
|
||||
const auth = useAuthStore();
|
||||
const data = ref<DashboardDto | null>(null);
|
||||
const loading = ref(true);
|
||||
|
||||
const maxCategoryCount = computed(() => {
|
||||
if (!data.value?.categoryStats?.length) return 1;
|
||||
return Math.max(...data.value.categoryStats.map((c) => c.count), 1);
|
||||
});
|
||||
|
||||
const maxTrendCount = computed(() => {
|
||||
if (!data.value?.trend?.length) return 1;
|
||||
return Math.max(...data.value.trend.map((t) => t.messages), 1);
|
||||
});
|
||||
|
||||
const sectionPercent = computed(() => {
|
||||
if (!data.value?.sectionsSummary?.total) return 100;
|
||||
return Math.round((data.value.sectionsSummary.visible / data.value.sectionsSummary.total) * 100);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
data.value = await api<DashboardDto>("/admin/dashboard");
|
||||
} catch (e) {
|
||||
void message.error(errorMessage(e));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeader title="داشبورد" subtitle="خلاصه وضعیت محتوای سایت">
|
||||
<RouterLink :to="{ name: 'product-new' }">
|
||||
<a-button type="primary">
|
||||
<template #icon><VuesaxIcon name="add" :size="16" /></template>
|
||||
محصول جدید
|
||||
</a-button>
|
||||
</RouterLink>
|
||||
<PageHeader title="داشبورد مدیریت" subtitle="نمای کلی آمار، نمودارها، پیامها و فعالیتهای اخیر سامانه">
|
||||
<div class="flex items-center gap-2">
|
||||
<RouterLink :to="{ name: 'sections' }">
|
||||
<a-button>
|
||||
<template #icon><VuesaxIcon name="row-vertical" :size="16" /></template>
|
||||
مدیریت بخشها
|
||||
</a-button>
|
||||
</RouterLink>
|
||||
<RouterLink :to="{ name: 'product-new' }">
|
||||
<a-button type="primary">
|
||||
<template #icon><VuesaxIcon name="add" :size="16" /></template>
|
||||
محصول جدید
|
||||
</a-button>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<a-spin :spinning="!data">
|
||||
<div class="grid grid-cols-2 gap-4 lg:grid-cols-5">
|
||||
<RouterLink :to="{ name: 'products' }">
|
||||
<a-card hoverable><a-statistic title="محصولات" :value="faNumber(data?.products ?? 0)" /></a-card>
|
||||
</RouterLink>
|
||||
<RouterLink :to="{ name: 'products', query: { status: 'draft' } }">
|
||||
<a-card hoverable><a-statistic title="پیشنویس" :value="faNumber(data?.draftProducts ?? 0)" /></a-card>
|
||||
</RouterLink>
|
||||
<RouterLink :to="{ name: 'categories' }">
|
||||
<a-card hoverable><a-statistic title="دستهبندیها" :value="faNumber(data?.categories ?? 0)" /></a-card>
|
||||
</RouterLink>
|
||||
<RouterLink :to="{ name: 'messages', query: { status: 'NEW' } }">
|
||||
<a-card hoverable>
|
||||
<a-statistic
|
||||
title="پیامهای جدید"
|
||||
:value="faNumber(data?.newMessages ?? 0)"
|
||||
:value-style="data?.newMessages ? { color: '#ff5a00' } : undefined"
|
||||
/>
|
||||
<a-spin :spinning="loading">
|
||||
<!-- 1. Top Statistics Row -->
|
||||
<div class="grid grid-cols-2 gap-3 lg:gap-4 lg:grid-cols-5">
|
||||
<RouterLink :to="{ name: 'products' }" class="active:scale-[0.98] transition-transform">
|
||||
<a-card hoverable class="h-full" :body-style="{ padding: '14px' }">
|
||||
<div class="flex items-center justify-between">
|
||||
<a-statistic title="کل محصولات" :value="faNumber(data?.products ?? 0)" />
|
||||
<div class="flex size-10 items-center justify-center rounded-xl bg-orange-50 text-orange-600">
|
||||
<VuesaxIcon name="bag-2" :size="20" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-gray-400">
|
||||
{{ faNumber((data?.products ?? 0) - (data?.draftProducts ?? 0)) }} محصول فعال روی سایت
|
||||
</div>
|
||||
</a-card>
|
||||
</RouterLink>
|
||||
<RouterLink :to="{ name: 'media' }">
|
||||
<a-card hoverable><a-statistic title="تصاویر" :value="faNumber(data?.media ?? 0)" /></a-card>
|
||||
|
||||
<RouterLink :to="{ name: 'products', query: { status: 'draft' } }" class="active:scale-[0.98] transition-transform">
|
||||
<a-card hoverable class="h-full" :body-style="{ padding: '14px' }">
|
||||
<div class="flex items-center justify-between">
|
||||
<a-statistic title="پیشنویسها" :value="faNumber(data?.draftProducts ?? 0)" />
|
||||
<div class="flex size-10 items-center justify-center rounded-xl bg-amber-50 text-amber-600">
|
||||
<VuesaxIcon name="document-text" :size="20" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-gray-400">نیاز به بازبینی و انتشار</div>
|
||||
</a-card>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink :to="{ name: 'categories' }" class="active:scale-[0.98] transition-transform">
|
||||
<a-card hoverable class="h-full" :body-style="{ padding: '14px' }">
|
||||
<div class="flex items-center justify-between">
|
||||
<a-statistic title="دستهبندیها" :value="faNumber(data?.categories ?? 0)" />
|
||||
<div class="flex size-10 items-center justify-center rounded-xl bg-blue-50 text-blue-600">
|
||||
<VuesaxIcon name="category" :size="20" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-gray-400">شاخههای تجهیزات ایمنی</div>
|
||||
</a-card>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink :to="{ name: 'messages', query: { status: 'NEW' } }" class="active:scale-[0.98] transition-transform">
|
||||
<a-card hoverable class="h-full" :body-style="{ padding: '14px' }">
|
||||
<div class="flex items-center justify-between">
|
||||
<a-statistic
|
||||
title="پیامهای جدید"
|
||||
:value="faNumber(data?.newMessages ?? 0)"
|
||||
:value-style="data?.newMessages ? { color: '#ff5a00', fontWeight: 'bold' } : undefined"
|
||||
/>
|
||||
<div
|
||||
class="flex size-10 items-center justify-center rounded-xl"
|
||||
:class="data?.newMessages ? 'bg-orange-100 text-orange-600 animate-pulse' : 'bg-gray-100 text-gray-500'"
|
||||
>
|
||||
<VuesaxIcon name="sms" :size="20" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-gray-400">
|
||||
از مجموع {{ faNumber(data?.messageBreakdown?.total ?? 0) }} پیام
|
||||
</div>
|
||||
</a-card>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink :to="{ name: 'media' }" class="max-lg:col-span-2 active:scale-[0.98] transition-transform">
|
||||
<a-card hoverable class="h-full" :body-style="{ padding: '14px' }">
|
||||
<div class="flex items-center justify-between">
|
||||
<a-statistic title="رسانهها و تصاویر" :value="faNumber(data?.media ?? 0)" />
|
||||
<div class="flex size-10 items-center justify-center rounded-xl bg-purple-50 text-purple-600">
|
||||
<VuesaxIcon name="gallery" :size="20" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-gray-400">تصاویر بهینهشده WebP</div>
|
||||
</a-card>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- 2. Quick Actions & Site Health Section -->
|
||||
<div class="mt-6 grid gap-6 lg:grid-cols-12">
|
||||
<!-- Quick Action Buttons -->
|
||||
<a-card title="دسترسیهای سریع" class="lg:col-span-8">
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
<RouterLink :to="{ name: 'product-new' }" class="block">
|
||||
<div class="flex flex-col items-center justify-center rounded-xl border border-gray-100 bg-gray-50/70 p-4 text-center transition-all hover:border-orange-200 hover:bg-orange-50/50 hover:shadow-sm">
|
||||
<div class="mb-2 flex size-11 items-center justify-center rounded-full bg-white text-orange-600 shadow-sm">
|
||||
<VuesaxIcon name="add" :size="22" />
|
||||
</div>
|
||||
<span class="text-xs font-bold text-gray-800">افزودن محصول</span>
|
||||
<span class="mt-1 text-[11px] text-gray-400">کالای ایمنی جدید</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink :to="{ name: 'category-new' }" class="block">
|
||||
<div class="flex flex-col items-center justify-center rounded-xl border border-gray-100 bg-gray-50/70 p-4 text-center transition-all hover:border-blue-200 hover:bg-blue-50/50 hover:shadow-sm">
|
||||
<div class="mb-2 flex size-11 items-center justify-center rounded-full bg-white text-blue-600 shadow-sm">
|
||||
<VuesaxIcon name="folder-add" :size="22" />
|
||||
</div>
|
||||
<span class="text-xs font-bold text-gray-800">دستهبندی جدید</span>
|
||||
<span class="mt-1 text-[11px] text-gray-400">گروه محصولات</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink :to="{ name: 'sections' }" class="block">
|
||||
<div class="flex flex-col items-center justify-center rounded-xl border border-gray-100 bg-gray-50/70 p-4 text-center transition-all hover:border-emerald-200 hover:bg-emerald-50/50 hover:shadow-sm">
|
||||
<div class="mb-2 flex size-11 items-center justify-center rounded-full bg-white text-emerald-600 shadow-sm">
|
||||
<VuesaxIcon name="row-vertical" :size="22" />
|
||||
</div>
|
||||
<span class="text-xs font-bold text-gray-800">بخشهای سایت</span>
|
||||
<span class="mt-1 text-[11px] text-gray-400">نمایش یا مخفیسازی</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink :to="{ name: 'media' }" class="block">
|
||||
<div class="flex flex-col items-center justify-center rounded-xl border border-gray-100 bg-gray-50/70 p-4 text-center transition-all hover:border-purple-200 hover:bg-purple-50/50 hover:shadow-sm">
|
||||
<div class="mb-2 flex size-11 items-center justify-center rounded-full bg-white text-purple-600 shadow-sm">
|
||||
<VuesaxIcon name="gallery-add" :size="22" />
|
||||
</div>
|
||||
<span class="text-xs font-bold text-gray-800">آپلود رسانه</span>
|
||||
<span class="mt-1 text-[11px] text-gray-400">تصاویر و اسناد</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- Section Visibility Health Card -->
|
||||
<a-card title="وضعیت بخشهای صفحه اصلی" class="lg:col-span-4">
|
||||
<template #extra>
|
||||
<RouterLink :to="{ name: 'sections' }" class="text-xs text-orange-600 hover:underline">
|
||||
مدیریت
|
||||
</RouterLink>
|
||||
</template>
|
||||
<div class="flex items-center gap-4">
|
||||
<a-progress
|
||||
type="circle"
|
||||
:percent="sectionPercent"
|
||||
:size="72"
|
||||
:stroke-color="{ '0%': '#ff8a3d', '100%': '#ff5a00' }"
|
||||
/>
|
||||
<div class="space-y-1.5 text-xs">
|
||||
<div class="font-bold text-gray-800">
|
||||
{{ faNumber(data?.sectionsSummary?.visible ?? 0) }} بخش از {{ faNumber(data?.sectionsSummary?.total ?? 12) }} فعال
|
||||
</div>
|
||||
<div class="text-gray-500">
|
||||
<span v-if="(data?.sectionsSummary?.hidden ?? 0) === 0" class="text-emerald-600 font-medium">
|
||||
تمامی بخشهای سایت در حال نمایش هستند
|
||||
</span>
|
||||
<span v-else class="text-amber-600 font-medium">
|
||||
{{ faNumber(data?.sectionsSummary?.hidden ?? 0) }} بخش غیرفعال / مخفی
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-[11px] text-gray-400">
|
||||
تغییر وضعیت آنی از منوی بخشهای سایت
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 3. Charts Row (نمودارهای تحلیلی) -->
|
||||
<div class="mt-6 grid gap-6 lg:grid-cols-12">
|
||||
<!-- Chart 1: Products distribution per category -->
|
||||
<a-card title="نمودار توزیع محصولات بر اساس دستهبندی" class="lg:col-span-7">
|
||||
<div v-if="!data?.categoryStats?.length" class="py-8">
|
||||
<a-empty description="دادهای موجود نیست" />
|
||||
</div>
|
||||
<div v-else class="space-y-3.5 pt-1">
|
||||
<div v-for="cat in data.categoryStats" :key="cat.id" class="space-y-1">
|
||||
<div class="flex items-center justify-between text-xs font-medium">
|
||||
<span class="text-gray-700">{{ cat.name }}</span>
|
||||
<span class="font-mono text-gray-500">{{ faNumber(cat.count) }} محصول</span>
|
||||
</div>
|
||||
<a-progress
|
||||
:percent="Math.round((cat.count / maxCategoryCount) * 100)"
|
||||
:show-info="false"
|
||||
:stroke-color="{ '0%': '#ffd0b5', '100%': '#ff5a00' }"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- Chart 2: Message Status Breakdown & Trend -->
|
||||
<a-card title="تحلیل پیامهای تماس دریافتی" class="lg:col-span-5">
|
||||
<!-- Message Status Chips -->
|
||||
<div class="grid grid-cols-3 gap-2 text-center">
|
||||
<div class="rounded-lg bg-orange-50 p-2.5">
|
||||
<div class="text-lg font-black text-orange-600">{{ faNumber(data?.messageBreakdown?.new ?? 0) }}</div>
|
||||
<div class="text-[11px] font-medium text-gray-600">جدید</div>
|
||||
</div>
|
||||
<div class="rounded-lg bg-blue-50 p-2.5">
|
||||
<div class="text-lg font-black text-blue-600">{{ faNumber(data?.messageBreakdown?.read ?? 0) }}</div>
|
||||
<div class="text-[11px] font-medium text-gray-600">خواندهشده</div>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2.5">
|
||||
<div class="text-lg font-black text-gray-600">{{ faNumber(data?.messageBreakdown?.archived ?? 0) }}</div>
|
||||
<div class="text-[11px] font-medium text-gray-600">بایگانی</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 7-Day Trend Bar Chart -->
|
||||
<div class="mt-5 border-t border-gray-100 pt-4">
|
||||
<div class="mb-3 flex items-center justify-between text-xs">
|
||||
<span class="font-bold text-gray-700">روند پیامها در ۷ روز اخیر</span>
|
||||
<span class="text-gray-400">نمودار روزانه</span>
|
||||
</div>
|
||||
<div class="flex h-28 items-end justify-between gap-1.5 sm:gap-2 pt-4">
|
||||
<div
|
||||
v-for="day in data?.trend ?? []"
|
||||
:key="day.date"
|
||||
class="flex flex-1 flex-col items-center gap-1"
|
||||
>
|
||||
<span v-if="day.messages > 0" class="text-[9px] font-bold text-orange-600 mb-0.5">
|
||||
{{ faNumber(day.messages) }}
|
||||
</span>
|
||||
<span v-else class="text-[9px] text-transparent mb-0.5 select-none">-</span>
|
||||
|
||||
<a-tooltip :title="`${day.dayName}: ${faNumber(day.messages)} پیام`">
|
||||
<div class="relative flex w-full justify-center">
|
||||
<div
|
||||
class="w-full max-w-[24px] rounded-t-md transition-all duration-300"
|
||||
:class="day.messages > 0 ? 'bg-orange-500 hover:bg-orange-600' : 'bg-gray-100'"
|
||||
:style="{
|
||||
height: `${Math.max(Math.round((day.messages / maxTrendCount) * 72), 6)}px`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<span class="text-[10px] text-gray-400 truncate max-w-[36px]">{{ day.dayName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 4. Tables / Activity Lists Row -->
|
||||
<div class="mt-6 grid gap-6 lg:grid-cols-2">
|
||||
<!-- Recent Messages -->
|
||||
<a-card title="آخرین پیامهای تماس">
|
||||
<template #extra><RouterLink :to="{ name: 'messages' }">همه پیامها</RouterLink></template>
|
||||
<template #extra>
|
||||
<RouterLink :to="{ name: 'messages' }" class="text-xs text-orange-600 hover:underline">
|
||||
مشاهده همه ←
|
||||
</RouterLink>
|
||||
</template>
|
||||
<a-empty v-if="!data?.recentMessages.length" description="پیامی ثبت نشده است" />
|
||||
<a-list v-else :data-source="data.recentMessages" item-layout="horizontal">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta :title="`${item.name} — ${item.org}`" :description="item.message || item.phone" />
|
||||
<a-list-item-meta
|
||||
:title="`${item.name} — ${item.org}`"
|
||||
:description="item.message || item.phone"
|
||||
/>
|
||||
<div class="text-left text-xs text-gray-400">
|
||||
<a-tag :color="item.status === 'NEW' ? 'orange' : 'default'">{{ MESSAGE_STATUS_LABELS[item.status as keyof typeof MESSAGE_STATUS_LABELS] }}</a-tag>
|
||||
<div class="mt-1">{{ jalali(item.createdAt) }}</div>
|
||||
<a-tag :color="item.status === 'NEW' ? 'orange' : item.status === 'READ' ? 'blue' : 'default'">
|
||||
{{ MESSAGE_STATUS_LABELS[item.status as keyof typeof MESSAGE_STATUS_LABELS] }}
|
||||
</a-tag>
|
||||
<div class="mt-1 font-mono text-[11px]">{{ jalali(item.createdAt) }}</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
|
||||
<a-card title="آخرین فعالیتها">
|
||||
<!-- Recent Audit Log / Activity -->
|
||||
<a-card title="آخرین فعالیتها و رویدادهای سیستم">
|
||||
<a-empty v-if="!data?.recentActivity.length" description="فعالیتی ثبت نشده است" />
|
||||
<a-timeline v-else class="!mt-2">
|
||||
<a-timeline-item v-for="a in data.recentActivity" :key="a.id">
|
||||
<a-timeline v-else class="!mt-2 max-h-[360px] overflow-y-auto pe-2">
|
||||
<a-timeline-item
|
||||
v-for="a in data.recentActivity"
|
||||
:key="a.id"
|
||||
:color="a.action === 'delete' ? 'red' : a.action === 'create' ? 'green' : 'blue'"
|
||||
>
|
||||
<div class="text-sm">
|
||||
<b>{{ activityLabel(a.action, a.entity) }}</b>
|
||||
<span v-if="a.summary" class="text-gray-600"> — {{ a.summary }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400">{{ a.user ?? "سیستم" }} · {{ jalali(a.createdAt) }}</div>
|
||||
<div class="mt-0.5 text-xs text-gray-400">
|
||||
{{ a.user ?? "سیستم" }} · {{ jalali(a.createdAt) }}
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</a-card>
|
||||
|
||||
@@ -176,6 +176,10 @@ async function submit() {
|
||||
}
|
||||
}
|
||||
|
||||
.login-page {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
.login-panel :deep(.ant-form-item-label > label) {
|
||||
color: #edede6 !important;
|
||||
}
|
||||
@@ -187,6 +191,12 @@ async function submit() {
|
||||
color: #edede6 !important;
|
||||
}
|
||||
|
||||
.login-panel :deep(.ant-input-affix-wrapper input.ant-input),
|
||||
.login-panel :deep(.ant-input-affix-wrapper input) {
|
||||
background: transparent !important;
|
||||
color: #edede6 !important;
|
||||
}
|
||||
|
||||
.login-panel :deep(.ant-input-affix-wrapper:hover),
|
||||
.login-panel :deep(.ant-input-affix-wrapper-focused),
|
||||
.login-panel :deep(.ant-input-affix-wrapper:focus-within) {
|
||||
@@ -198,10 +208,23 @@ async function submit() {
|
||||
color: #8e9ab0 !important;
|
||||
}
|
||||
|
||||
.login-panel :deep(.ant-input-password-icon) {
|
||||
.login-panel :deep(.ant-input-password-icon),
|
||||
.login-panel :deep(.ant-input-suffix) {
|
||||
color: #8e9ab0 !important;
|
||||
}
|
||||
|
||||
/* Fix browser autofill white background and black text */
|
||||
.login-panel :deep(input:-webkit-autofill),
|
||||
.login-panel :deep(input:-webkit-autofill:hover),
|
||||
.login-panel :deep(input:-webkit-autofill:focus),
|
||||
.login-panel :deep(input:-webkit-autofill:active) {
|
||||
-webkit-box-shadow: 0 0 0 1000px #152038 inset !important;
|
||||
box-shadow: 0 0 0 1000px #152038 inset !important;
|
||||
-webkit-text-fill-color: #edede6 !important;
|
||||
caret-color: #edede6 !important;
|
||||
transition: background-color 99999s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.login-panel :deep(.ant-form-item-explain-error) {
|
||||
color: #ff7a2a;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { MediaDto, Paginated } from "@imen/shared";
|
||||
import { message } from "ant-design-vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { api, errorMessage } from "@/api/http";
|
||||
import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { faNumber, fileSize, jalali } from "@/utils/format";
|
||||
|
||||
const auth = useAuthStore();
|
||||
const items = ref<MediaDto[]>([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
@@ -16,6 +18,13 @@ const uploading = ref(0);
|
||||
const selected = ref<MediaDto | null>(null);
|
||||
const altDraft = ref("");
|
||||
const PAGE_SIZE = 36;
|
||||
const isMobile = ref(false);
|
||||
|
||||
function updateMobile() {
|
||||
if (typeof window !== "undefined") {
|
||||
isMobile.value = window.innerWidth < 1024;
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
@@ -82,7 +91,17 @@ async function copyUrl(url: string) {
|
||||
void message.success("آدرس کپی شد");
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
onMounted(() => {
|
||||
updateMobile();
|
||||
window.addEventListener("resize", updateMobile);
|
||||
void load();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.removeEventListener("resize", updateMobile);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -100,51 +119,121 @@ onMounted(load);
|
||||
v-model:value="q"
|
||||
allow-clear
|
||||
placeholder="جستجو در نام فایل یا متن جایگزین"
|
||||
class="mb-4 max-w-md"
|
||||
class="mb-4 w-full md:max-w-md"
|
||||
@search="() => { page = 1; load(); }"
|
||||
/>
|
||||
<a-spin :spinning="loading">
|
||||
<a-empty v-if="!items.length && !loading" description="تصویری پیدا نشد" />
|
||||
<div class="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-6 xl:grid-cols-9">
|
||||
<div class="grid grid-cols-3 gap-2.5 sm:grid-cols-4 md:grid-cols-6 xl:grid-cols-9">
|
||||
<button
|
||||
v-for="m in items"
|
||||
:key="m.id"
|
||||
type="button"
|
||||
class="overflow-hidden rounded-lg border border-gray-200 bg-gray-50 transition hover:border-[#ff5a00]"
|
||||
class="group relative overflow-hidden rounded-xl border border-gray-200 bg-gray-50 transition active:scale-95 hover:border-[#ff5a00] focus:outline-none"
|
||||
:title="m.originalName"
|
||||
@click="open(m)"
|
||||
>
|
||||
<img :src="m.url" :alt="m.alt" class="aspect-square w-full object-contain" loading="lazy" />
|
||||
<img :src="m.url" :alt="m.alt" class="aspect-square w-full object-contain p-1" loading="lazy" />
|
||||
<div class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/60 to-transparent p-1 text-[9px] text-white opacity-0 transition group-hover:opacity-100 max-lg:hidden truncate">
|
||||
{{ m.originalName }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</a-spin>
|
||||
<div class="mt-4 flex justify-center">
|
||||
<a-pagination v-model:current="page" :total="total" :page-size="PAGE_SIZE" :show-size-changer="false" @change="load" />
|
||||
<a-pagination
|
||||
v-model:current="page"
|
||||
:total="total"
|
||||
:page-size="PAGE_SIZE"
|
||||
:show-size-changer="false"
|
||||
:simple="isMobile"
|
||||
@change="load"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-drawer :open="!!selected" title="جزئیات تصویر" :width="380" placement="left" @close="selected = null">
|
||||
<!-- Mobile Floating Upload Button -->
|
||||
<div class="lg:hidden fixed bottom-20 left-4 z-30">
|
||||
<a-upload accept="image/jpeg,image/png,image/webp,image/avif,image/gif" multiple :show-upload-list="false" :before-upload="upload">
|
||||
<button
|
||||
type="button"
|
||||
class="flex size-13 items-center justify-center rounded-full bg-[#ff5a00] text-white shadow-lg shadow-orange-500/30 transition-transform active:scale-95"
|
||||
title="آپلود تصویر"
|
||||
:disabled="uploading > 0"
|
||||
>
|
||||
<VuesaxIcon v-if="uploading === 0" name="document-upload" :size="24" />
|
||||
<span v-else class="text-xs font-bold">{{ uploading }}</span>
|
||||
</button>
|
||||
</a-upload>
|
||||
</div>
|
||||
|
||||
<!-- Image Detail Drawer (Side on Desktop, Bottom Sheet on Mobile) -->
|
||||
<a-drawer
|
||||
:open="!!selected"
|
||||
title="جزئیات تصویر"
|
||||
:width="isMobile ? '100%' : 380"
|
||||
:height="isMobile ? '82vh' : undefined"
|
||||
:placement="isMobile ? 'bottom' : 'left'"
|
||||
:body-style="{ paddingBottom: isMobile ? '80px' : '24px' }"
|
||||
@close="selected = null"
|
||||
>
|
||||
<template v-if="selected">
|
||||
<img :src="selected.url" :alt="selected.alt" class="w-full rounded-lg border border-gray-200 bg-gray-50 object-contain" />
|
||||
<a-descriptions :column="1" size="small" class="mt-4">
|
||||
<a-descriptions-item label="نام فایل"><span dir="ltr">{{ selected.originalName }}</span></a-descriptions-item>
|
||||
<div class="overflow-hidden rounded-xl border border-gray-200 bg-gray-50 flex items-center justify-center p-2">
|
||||
<img :src="selected.url" :alt="selected.alt" class="max-h-60 w-full object-contain" />
|
||||
</div>
|
||||
|
||||
<a-descriptions :column="isMobile ? 2 : 1" size="small" class="mt-4">
|
||||
<a-descriptions-item label="نام فایل" :span="isMobile ? 2 : 1">
|
||||
<span dir="ltr" class="truncate block max-w-[240px] text-xs font-mono text-gray-700">{{ selected.originalName }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="ابعاد">
|
||||
<span dir="ltr">{{ selected.width }}×{{ selected.height }}</span>
|
||||
<span dir="ltr" class="text-xs font-mono">{{ selected.width }}×{{ selected.height }}</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="حجم">{{ fileSize(selected.size) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="تاریخ">{{ jalali(selected.createdAt) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="تاریخ" :span="isMobile ? 2 : 1">{{ jalali(selected.createdAt) }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-form layout="vertical" class="mt-4">
|
||||
<a-form-item label="متن جایگزین (alt)">
|
||||
<a-input v-model:value="altDraft" />
|
||||
<a-form-item label="متن جایگزین (alt)" class="!mb-3">
|
||||
<a-input v-model:value="altDraft" placeholder="توضیح کوتاه برای سئو و دسترسپذیری" />
|
||||
</a-form-item>
|
||||
<a-space wrap>
|
||||
<a-button type="primary" @click="saveAlt">ذخیره</a-button>
|
||||
<a-button @click="copyUrl(selected.url)"><template #icon><VuesaxIcon name="copy" :size="16" /></template>کپی آدرس</a-button>
|
||||
<a-popconfirm title="این تصویر حذف شود؟" ok-text="حذف" cancel-text="انصراف" @confirm="remove">
|
||||
<a-button danger><template #icon><VuesaxIcon name="trash" :size="16" /></template>حذف</a-button>
|
||||
|
||||
<!-- Actions: Desktop row vs Mobile full touch grid -->
|
||||
<div class="max-lg:grid max-lg:grid-cols-2 max-lg:gap-2 max-lg:mt-4 max-lg:hidden">
|
||||
<a-space wrap>
|
||||
<a-button type="primary" @click="saveAlt">ذخیره</a-button>
|
||||
<a-button @click="copyUrl(selected.url)"><template #icon><VuesaxIcon name="copy" :size="16" /></template>کپی آدرس</a-button>
|
||||
<a-popconfirm
|
||||
v-if="auth.isAdmin"
|
||||
title="این تصویر حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@confirm="remove"
|
||||
>
|
||||
<a-button danger><template #icon><VuesaxIcon name="trash" :size="16" /></template>حذف</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="lg:hidden grid grid-cols-2 gap-2 mt-4">
|
||||
<a-button type="primary" class="h-10 !font-bold" @click="saveAlt">ذخیره متن</a-button>
|
||||
<a-button class="h-10 !flex !items-center !justify-center !gap-1" @click="copyUrl(selected.url)">
|
||||
<template #icon><VuesaxIcon name="copy" :size="16" /></template>
|
||||
<span>کپی آدرس</span>
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
v-if="auth.isAdmin"
|
||||
title="این تصویر حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@confirm="remove"
|
||||
>
|
||||
<a-button danger class="col-span-2 h-10 !flex !items-center !justify-center !gap-1">
|
||||
<template #icon><VuesaxIcon name="trash" :size="16" /></template>
|
||||
<span>حذف تصویر</span>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-form>
|
||||
</template>
|
||||
</a-drawer>
|
||||
|
||||
@@ -7,13 +7,15 @@ import {
|
||||
type Paginated,
|
||||
} from "@imen/shared";
|
||||
import { message } from "ant-design-vue";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { api, errorMessage } from "@/api/http";
|
||||
import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { faNumber, jalali } from "@/utils/format";
|
||||
|
||||
const auth = useAuthStore();
|
||||
const route = useRoute();
|
||||
/** "ALL" or one status */
|
||||
const status = ref<MessageStatus | "ALL">((route.query.status as MessageStatus) || "ALL");
|
||||
@@ -23,8 +25,11 @@ const page = ref(1);
|
||||
const rows = ref<ContactMessageDto[]>([]);
|
||||
const total = ref(0);
|
||||
const loading = ref(false);
|
||||
const expandedIds = ref<number[]>([]);
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
const totalPages = computed(() => Math.ceil(total.value / PAGE_SIZE) || 1);
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -44,11 +49,24 @@ async function setStatus(row: ContactMessageDto, s: MessageStatus) {
|
||||
try {
|
||||
await api(`/admin/messages/${row.id}`, { method: "PATCH", body: { status: s } });
|
||||
row.status = s;
|
||||
void message.success(`وضعیت پیام به «${MESSAGE_STATUS_LABELS[s]}» تغییر کرد`);
|
||||
} catch (e) {
|
||||
void message.error(errorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
function toggleExpand(row: ContactMessageDto) {
|
||||
const idx = expandedIds.value.indexOf(row.id);
|
||||
if (idx > -1) {
|
||||
expandedIds.value.splice(idx, 1);
|
||||
} else {
|
||||
expandedIds.value.push(row.id);
|
||||
if (row.status === "NEW") {
|
||||
void setStatus(row, "READ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** opening a new message marks it as read */
|
||||
function onExpand(expanded: boolean, row: ContactMessageDto) {
|
||||
if (expanded && row.status === "NEW") void setStatus(row, "READ");
|
||||
@@ -64,6 +82,12 @@ async function remove(row: ContactMessageDto) {
|
||||
}
|
||||
}
|
||||
|
||||
function onMobilePageChange(p: number) {
|
||||
if (p < 1 || p > totalPages.value) return;
|
||||
page.value = p;
|
||||
void load();
|
||||
}
|
||||
|
||||
const STATUS_COLORS: Record<MessageStatus, string> = { NEW: "orange", READ: "blue", ARCHIVED: "default" };
|
||||
const columns = [
|
||||
{ title: "نام", dataIndex: "name" },
|
||||
@@ -82,11 +106,28 @@ onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeader title="پیامهای تماس" :subtitle="`${faNumber(total)} پیام — درخواستهای ثبتشده از فرم مشاوره سایت`">
|
||||
<a-segmented v-model:value="status" :options="FILTER_OPTIONS" />
|
||||
<PageHeader title="پیامهای تماس" :subtitle="`${faNumber(total)} پیام — استعلامها و درخواستهای ثبتشده از سایت`">
|
||||
<div class="!hidden lg:!block">
|
||||
<a-segmented v-model:value="status" :options="FILTER_OPTIONS" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<a-card :body-style="{ padding: 0 }">
|
||||
<!-- Mobile Segmented Filter Row (Mobile Only) -->
|
||||
<div class="mobile-filter-bar sticky top-[52px] z-20 bg-white/95 backdrop-blur-md -mx-3 px-3 py-2 border-b border-slate-100 flex items-center gap-1.5 overflow-x-auto no-scrollbar mb-3 lg:!hidden">
|
||||
<button
|
||||
v-for="opt in FILTER_OPTIONS"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
class="px-3 py-1.5 rounded-full text-xs font-bold transition shrink-0 select-none cursor-pointer"
|
||||
:class="status === opt.value ? 'bg-orange-600 text-white shadow-xs' : 'bg-slate-100 text-slate-600 active:bg-slate-200'"
|
||||
@click="status = opt.value as any"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Table View (lg and up) -->
|
||||
<a-card class="!hidden lg:!block" :body-style="{ padding: 0 }">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="rows"
|
||||
@@ -101,7 +142,7 @@ onMounted(load);
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'phone'">
|
||||
<a :href="`tel:${record.phone}`" dir="ltr">{{ record.phone }}</a>
|
||||
<a :href="`tel:${record.phone}`" dir="ltr" class="hover:text-orange-600 font-mono">{{ record.phone }}</a>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'date'">{{ jalali(record.createdAt) }}</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
@@ -116,11 +157,136 @@ onMounted(load);
|
||||
</a-select>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-popconfirm title="این پیام حذف شود؟" ok-text="حذف" cancel-text="انصراف" @confirm="remove(record as ContactMessageDto)">
|
||||
<a-popconfirm
|
||||
v-if="auth.isAdmin"
|
||||
title="این پیام حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@confirm="remove(record as ContactMessageDto)"
|
||||
>
|
||||
<a-button size="small" danger><template #icon><VuesaxIcon name="trash" :size="16" /></template></a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- Mobile Message Cards View (Mobile Only) -->
|
||||
<div class="mobile-messages-view block lg:!hidden pb-20">
|
||||
<a-spin :spinning="loading">
|
||||
<a-empty v-if="!rows.length && !loading" description="پیامی با این وضعیت موجود نیست" class="py-12" />
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="record in rows"
|
||||
:key="record.id"
|
||||
class="bg-white rounded-xl border border-slate-200/80 p-3.5 shadow-2xs transition"
|
||||
:class="{ 'border-orange-300 ring-2 ring-orange-100': record.status === 'NEW' }"
|
||||
>
|
||||
<!-- Top Row: Sender Info + Status Pill -->
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="font-bold text-[14px] text-slate-800">{{ record.name }}</span>
|
||||
<span v-if="record.status === 'NEW'" class="size-2 rounded-full bg-orange-500 animate-pulse" />
|
||||
</div>
|
||||
<div v-if="record.org" class="text-xs text-slate-500 font-medium mt-0.5">
|
||||
{{ record.org }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-tag :color="STATUS_COLORS[record.status as MessageStatus]" class="!m-0 !text-[11px] !font-bold">
|
||||
{{ MESSAGE_STATUS_LABELS[record.status as keyof typeof MESSAGE_STATUS_LABELS] }}
|
||||
</a-tag>
|
||||
</div>
|
||||
|
||||
<!-- Middle Row: Date & Call Button -->
|
||||
<div class="flex items-center justify-between border-t border-slate-100 pt-2.5 mt-2.5">
|
||||
<span class="text-[11px] text-slate-400 font-mono">
|
||||
{{ jalali(record.createdAt) }}
|
||||
</span>
|
||||
|
||||
<!-- Direct Call Button -->
|
||||
<a
|
||||
:href="'tel:' + record.phone"
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald-50 text-emerald-700 border border-emerald-200/70 font-bold text-xs active:bg-emerald-100 active:scale-95 transition"
|
||||
title="تماس مستقیم با مشتری"
|
||||
>
|
||||
<Phone :size="13" class="text-emerald-600 shrink-0" />
|
||||
<span dir="ltr" class="font-mono">{{ record.phone }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Message Body Preview & Full Content -->
|
||||
<div class="mt-3 cursor-pointer" @click="toggleExpand(record)">
|
||||
<div
|
||||
v-if="!expandedIds.includes(record.id)"
|
||||
class="text-xs text-slate-600 line-clamp-2 leading-relaxed bg-slate-50/70 p-2.5 rounded-lg border border-slate-100"
|
||||
>
|
||||
{{ record.message || "— بدون توضیح —" }}
|
||||
<div class="text-[10px] text-orange-600 font-bold mt-1">مشاهده متن کامل پیام ←</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="text-xs text-slate-700 whitespace-pre-line leading-relaxed bg-orange-50/30 p-3 rounded-lg border border-orange-100"
|
||||
>
|
||||
{{ record.message || "— بدون توضیح —" }}
|
||||
<div class="text-[10px] text-slate-400 font-medium mt-2">بستن متن پیام ↑</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Actions: Status Changer + Delete -->
|
||||
<div class="flex items-center justify-between border-t border-slate-100 pt-2.5 mt-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-[11px] text-slate-400">وضعیت:</span>
|
||||
<a-select
|
||||
:value="record.status"
|
||||
size="small"
|
||||
class="w-28 !text-xs"
|
||||
:options="STATUS_OPTIONS"
|
||||
@change="(v: unknown) => setStatus(record, v as MessageStatus)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-popconfirm
|
||||
v-if="auth.isAdmin"
|
||||
title="این پیام حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-rose-500 bg-rose-50 hover:bg-rose-100 active:bg-rose-200 transition"
|
||||
title="حذف پیام"
|
||||
>
|
||||
<VuesaxIcon name="trash" :size="16" />
|
||||
</button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Pagination -->
|
||||
<div v-if="totalPages > 1" class="flex items-center justify-between gap-3 pt-4 px-1">
|
||||
<a-button
|
||||
:disabled="page <= 1"
|
||||
class="!flex-1 !h-10 !rounded-xl !font-medium"
|
||||
@click="onMobilePageChange(page - 1)"
|
||||
>
|
||||
صفحه قبلی
|
||||
</a-button>
|
||||
<span class="text-xs text-slate-500 font-mono">
|
||||
{{ page.toLocaleString("fa-IR") }} / {{ totalPages.toLocaleString("fa-IR") }}
|
||||
</span>
|
||||
<a-button
|
||||
:disabled="page >= totalPages"
|
||||
class="!flex-1 !h-10 !rounded-xl !font-medium"
|
||||
@click="onMobilePageChange(page + 1)"
|
||||
>
|
||||
صفحه بعدی
|
||||
</a-button>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -59,6 +59,16 @@ const groups = ref<AdminCategoryDetail["groups"]>([]);
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
const mobileTab = ref<"general" | "specs" | "desc" | "media" | "seo">("general");
|
||||
|
||||
const MOBILE_TABS = [
|
||||
{ key: "general", label: "اطلاعات اصلی" },
|
||||
{ key: "specs", label: "مشخصات فنی" },
|
||||
{ key: "desc", label: "توضیحات و کاربرد" },
|
||||
{ key: "media", label: "تصاویر و سفارش" },
|
||||
{ key: "seo", label: "سئو و انتشار" },
|
||||
] as const;
|
||||
|
||||
const category = computed(() => categories.value.find((c) => c.id === model.value.categoryId));
|
||||
/** a-select clears to undefined, the API expects null */
|
||||
const groupId = computed({
|
||||
@@ -161,25 +171,48 @@ onMounted(load);
|
||||
|
||||
<template>
|
||||
<PageHeader :title="id ? `ویرایش محصول: ${model.name}` : 'محصول جدید'" back="/products">
|
||||
<a
|
||||
v-if="id && category?.isPublished && model.isPublished"
|
||||
:href="`${SITE_URL}/products/${model.slug}`"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<a-button>مشاهده در سایت</a-button>
|
||||
</a>
|
||||
<a-button type="primary" :loading="saving" @click="save">
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
ذخیره
|
||||
</a-button>
|
||||
<div class="!hidden lg:!flex items-center gap-2">
|
||||
<a
|
||||
v-if="id && category?.isPublished && model.isPublished"
|
||||
:href="`${SITE_URL}/products/${model.slug}`"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<a-button>مشاهده در سایت</a-button>
|
||||
</a>
|
||||
<a-button type="primary" :loading="saving" @click="save">
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
ذخیره
|
||||
</a-button>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-form layout="vertical" @submit.prevent="save">
|
||||
<a-form layout="vertical" class="pb-24 lg:pb-6" @submit.prevent="save">
|
||||
<!-- Mobile Segmented Navigation Tabs -->
|
||||
<div
|
||||
class="mobile-edit-tabs lg:!hidden flex items-center gap-1.5 overflow-x-auto pb-2.5 mb-3 sticky top-[52px] z-20 bg-white/95 backdrop-blur-md -mx-3 px-3 pt-1 border-b border-slate-100 no-scrollbar"
|
||||
>
|
||||
<button
|
||||
v-for="t in MOBILE_TABS"
|
||||
:key="t.key"
|
||||
type="button"
|
||||
class="px-3 py-1.5 rounded-full text-xs font-bold transition shrink-0 select-none cursor-pointer"
|
||||
:class="
|
||||
mobileTab === t.key
|
||||
? 'bg-orange-600 text-white shadow-xs'
|
||||
: 'bg-slate-100 text-slate-600 active:bg-slate-200'
|
||||
"
|
||||
@click="mobileTab = t.key"
|
||||
>
|
||||
{{ t.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-3">
|
||||
<div class="space-y-6 xl:col-span-2">
|
||||
<a-card title="اطلاعات محصول">
|
||||
<!-- Card 1: Main Info -->
|
||||
<a-card title="اطلاعات محصول" :class="mobileTab !== 'general' && 'max-lg:hidden'">
|
||||
<div class="grid gap-x-4 md:grid-cols-2">
|
||||
<FormField path="name" label="نام محصول" required>
|
||||
<a-input v-model:value="model.name" />
|
||||
@@ -206,7 +239,8 @@ onMounted(load);
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card title="توضیحات کامل (صفحه محصول)">
|
||||
<!-- Card 2: Details Description -->
|
||||
<a-card title="توضیحات کامل (صفحه محصول)" :class="mobileTab !== 'desc' && 'max-lg:hidden'">
|
||||
<FormField path="details" hint="برای شروع پاراگراف جدید یک خط خالی بگذارید. اگر خالی بماند، توضیح دسته نمایش داده میشود.">
|
||||
<a-textarea
|
||||
v-model:value="model.details"
|
||||
@@ -216,7 +250,8 @@ onMounted(load);
|
||||
</FormField>
|
||||
</a-card>
|
||||
|
||||
<a-card title="ویژگیها و کاربردها">
|
||||
<!-- Card 3: Features & Applications -->
|
||||
<a-card title="ویژگیها و کاربردها" :class="mobileTab !== 'desc' && 'max-lg:hidden'">
|
||||
<FormField path="features" label="ویژگیهای کلیدی" hint="هر ویژگی در یک خط">
|
||||
<LinesInput v-model="model.features" :rows="4" placeholder="مثلاً: زیره ضد لغزش و مقاوم در برابر روغن" />
|
||||
</FormField>
|
||||
@@ -225,7 +260,8 @@ onMounted(load);
|
||||
</FormField>
|
||||
</a-card>
|
||||
|
||||
<a-card title="مشخصات فنی" :body-style="{ background: '#fafafa' }">
|
||||
<!-- Card 4: Technical Attributes & Specifications -->
|
||||
<a-card title="مشخصات فنی" :body-style="{ background: '#fafafa' }" :class="mobileTab !== 'specs' && 'max-lg:hidden'">
|
||||
<p class="mb-4 text-sm text-gray-500">
|
||||
ردیفهای جدول مشخصات صفحه محصول (مثلاً «جنس رویه» ← «چرم طبیعی»). کد، دسته، برند و موارد زیر بهصورت خودکار به جدول اضافه میشوند.
|
||||
</p>
|
||||
@@ -260,40 +296,19 @@ onMounted(load);
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card title="گالری تصاویر">
|
||||
<p class="mb-4 text-sm text-gray-500">تصاویر بیشتر محصول؛ در صفحه محصول بعد از تصویر اصلی نمایش داده میشوند.</p>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div v-for="m in gallery" :key="m.id" class="relative size-24 overflow-hidden rounded-lg border border-gray-200 bg-gray-50">
|
||||
<img :src="m.url" alt="" class="size-full object-contain" />
|
||||
<button
|
||||
type="button"
|
||||
class="absolute end-1 top-1 flex size-6 items-center justify-center rounded-full bg-white/90 text-red-500 shadow"
|
||||
title="حذف از گالری"
|
||||
@click="removeFromGallery(m.id)"
|
||||
>
|
||||
<VuesaxIcon name="close-circle" :size="16" class="text-xs" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex size-24 items-center justify-center rounded-lg border border-dashed border-gray-300 text-gray-400 hover:border-[#ff5a00] hover:text-[#ff5a00]"
|
||||
@click="galleryPicker = true"
|
||||
>
|
||||
<VuesaxIcon name="add" :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
<MediaPicker v-model:open="galleryPicker" @select="addToGallery" />
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<a-card title="انتشار">
|
||||
<!-- Card: Publishing (Desktop & Mobile SEO tab) -->
|
||||
<a-card title="انتشار" :class="mobileTab !== 'seo' && 'max-lg:hidden'">
|
||||
<FormField path="isPublished">
|
||||
<a-switch v-model:checked="model.isPublished" />
|
||||
<span class="ms-2">{{ model.isPublished ? "در سایت نمایش داده میشود" : "پیشنویس (مخفی)" }}</span>
|
||||
</FormField>
|
||||
</a-card>
|
||||
<a-card title="دستهبندی">
|
||||
|
||||
<!-- Card: Category & Group (General tab on mobile) -->
|
||||
<a-card title="دستهبندی" :class="mobileTab !== 'general' && 'max-lg:hidden'">
|
||||
<FormField path="categoryId" label="دسته" required>
|
||||
<a-select
|
||||
v-model:value="model.categoryId"
|
||||
@@ -312,12 +327,56 @@ onMounted(load);
|
||||
/>
|
||||
</FormField>
|
||||
</a-card>
|
||||
<a-card title="تصویر اصلی">
|
||||
<FormField path="imageId" hint="بدون تصویر، کاور دسته نمایش داده میشود">
|
||||
<ImageField :url="image?.url" clearable @pick="pickImage" @clear="image = null; model.imageId = null" />
|
||||
</FormField>
|
||||
|
||||
<!-- Card: Product Images (Cover + Gallery) -->
|
||||
<a-card title="تصاویر محصول" :class="mobileTab !== 'media' && 'max-lg:hidden'">
|
||||
<!-- Cover / Main Image -->
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-xs font-bold text-slate-700">تصویر اصلی (کاور)</span>
|
||||
<span class="text-[11px] text-slate-400">نمایش در لیستها و کارتها</span>
|
||||
</div>
|
||||
<FormField path="imageId" hint="در صورت عدم انتخاب، کاور دستهبندی استفاده میشود">
|
||||
<ImageField :url="image?.url" clearable @pick="pickImage" @clear="image = null; model.imageId = null" />
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<div class="my-5 border-t border-slate-100" />
|
||||
|
||||
<!-- Gallery Images -->
|
||||
<div>
|
||||
<div class="mb-1.5 flex items-center justify-between">
|
||||
<span class="text-xs font-bold text-slate-700">گالری تصاویر بیشتر</span>
|
||||
<span v-if="gallery.length" class="text-[11px] text-orange-600 font-mono font-bold">{{ gallery.length }} تصویر</span>
|
||||
</div>
|
||||
<p class="mb-3 text-xs text-slate-400">تصاویری که در اسلایدر جزئیات محصول نمایش داده میشوند.</p>
|
||||
<div class="flex flex-wrap gap-2.5">
|
||||
<div v-for="m in gallery" :key="m.id" class="relative size-20 overflow-hidden rounded-lg border border-slate-200 bg-slate-50">
|
||||
<img :src="m.url" alt="" class="size-full object-contain p-1" />
|
||||
<button
|
||||
type="button"
|
||||
class="absolute end-1 top-1 flex size-5 items-center justify-center rounded-full bg-red-500 text-white shadow-xs transition hover:bg-red-600 cursor-pointer"
|
||||
title="حذف از گالری"
|
||||
@click="removeFromGallery(m.id)"
|
||||
>
|
||||
<VuesaxIcon name="close-circle" :size="12" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex size-20 flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-slate-300 text-slate-400 hover:border-[#ff5a00] hover:text-[#ff5a00] hover:bg-orange-50/30 transition cursor-pointer"
|
||||
@click="galleryPicker = true"
|
||||
>
|
||||
<VuesaxIcon name="add" :size="18" />
|
||||
<span class="text-[10px] font-medium">افزودن</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<MediaPicker v-model:open="galleryPicker" @select="addToGallery" />
|
||||
</a-card>
|
||||
<a-card title="برند و سفارش">
|
||||
|
||||
<!-- Card 9: Brand and Ordering (Media tab on mobile) -->
|
||||
<a-card title="برند و سفارش" :class="mobileTab !== 'media' && 'max-lg:hidden'">
|
||||
<FormField path="brand" label="برند">
|
||||
<a-input v-model:value="model.brand" />
|
||||
</FormField>
|
||||
@@ -331,7 +390,9 @@ onMounted(load);
|
||||
<a-input v-model:value="model.minOrder" placeholder="مثلاً: ۱ کارتن" />
|
||||
</FormField>
|
||||
</a-card>
|
||||
<a-card title="سئو">
|
||||
|
||||
<!-- Card 10: SEO (SEO tab on mobile) -->
|
||||
<a-card title="سئو" :class="mobileTab !== 'seo' && 'max-lg:hidden'">
|
||||
<FormField path="seoTitle" label="عنوان صفحه" hint="خالی = نام محصول">
|
||||
<a-input v-model:value="model.seoTitle" :maxlength="120" show-count />
|
||||
</FormField>
|
||||
@@ -341,6 +402,41 @@ onMounted(load);
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Sticky Bottom Action Bar -->
|
||||
<div
|
||||
class="fixed bottom-0 start-0 end-0 z-30 bg-white/95 backdrop-blur-md border-t border-slate-200/80 p-3 shadow-lg flex items-center justify-between gap-3 lg:!hidden"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<a-switch v-model:checked="model.isPublished" size="small" />
|
||||
<span class="text-xs" :class="model.isPublished ? 'text-emerald-600 font-bold' : 'text-slate-400'">
|
||||
{{ model.isPublished ? "منتشرشده" : "پیشنویس" }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
v-if="id && category?.isPublished && model.isPublished"
|
||||
:href="`${SITE_URL}/products/${model.slug}`"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="size-10 rounded-xl bg-slate-100 flex items-center justify-center text-slate-600 hover:text-orange-600 active:bg-slate-200"
|
||||
title="مشاهده در سایت"
|
||||
>
|
||||
<VuesaxIcon name="export" :size="18" />
|
||||
</a>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
class="!h-10 !px-5 !rounded-xl !font-bold !flex !items-center !gap-1.5 !shadow-sm"
|
||||
:loading="saving"
|
||||
@click="save"
|
||||
>
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
ذخیره محصول
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
@@ -7,9 +7,11 @@ import { api, errorMessage } from "@/api/http";
|
||||
import type { AdminCategory, AdminCategoryDetail, AdminProduct } from "@/api/types";
|
||||
import AdminPage from "@/components/AdminPage.vue";
|
||||
import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
const tableShell = ref<HTMLElement | null>(null);
|
||||
const scrollY = ref(360);
|
||||
let tableResizeObserver: ResizeObserver | null = null;
|
||||
@@ -44,11 +46,62 @@ const loading = ref(false);
|
||||
const categories = ref<AdminCategory[]>([]);
|
||||
const groups = ref<AdminCategoryDetail["groups"]>([]);
|
||||
const filtersOpen = ref(false);
|
||||
const mobileFilterDrawer = ref(false);
|
||||
|
||||
const activeFilterCount = computed(
|
||||
() => [filters.categoryId, filters.groupId, filters.status].filter(Boolean).length,
|
||||
);
|
||||
|
||||
const activeCategoryName = computed(
|
||||
() => categories.value.find((c) => c.id === filters.categoryId)?.name,
|
||||
);
|
||||
|
||||
const activeGroupName = computed(
|
||||
() => groups.value.find((g) => g.id === filters.groupId)?.name,
|
||||
);
|
||||
|
||||
const totalPages = computed(() => Math.ceil(total.value / filters.pageSize) || 1);
|
||||
|
||||
function clearAllFilters() {
|
||||
filters.categoryId = undefined;
|
||||
filters.groupId = undefined;
|
||||
filters.status = undefined;
|
||||
filters.q = "";
|
||||
filters.page = 1;
|
||||
void load();
|
||||
}
|
||||
|
||||
function clearCategoryFilter() {
|
||||
filters.categoryId = undefined;
|
||||
filters.groupId = undefined;
|
||||
filters.page = 1;
|
||||
void load();
|
||||
}
|
||||
|
||||
function clearGroupFilter() {
|
||||
filters.groupId = undefined;
|
||||
filters.page = 1;
|
||||
void load();
|
||||
}
|
||||
|
||||
function clearStatusFilter() {
|
||||
filters.status = undefined;
|
||||
filters.page = 1;
|
||||
void load();
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
filters.q = "";
|
||||
filters.page = 1;
|
||||
void load();
|
||||
}
|
||||
|
||||
function onMobilePageChange(p: number) {
|
||||
if (p < 1 || p > totalPages.value) return;
|
||||
filters.page = p;
|
||||
void load();
|
||||
}
|
||||
|
||||
/** products without their own photo show the category cover (same fallback as the website) */
|
||||
const thumbOf = (row: AdminProduct) =>
|
||||
row.image
|
||||
@@ -219,8 +272,22 @@ onBeforeUnmount(() => {
|
||||
<template>
|
||||
<AdminPage lock-body>
|
||||
<template #head>
|
||||
<div class="toolbar">
|
||||
<!-- Desktop Toolbar (lg and up) -->
|
||||
<div class="toolbar !hidden lg:!flex">
|
||||
<div class="toolbar__start">
|
||||
<a-input-search
|
||||
v-model:value="filters.q"
|
||||
allow-clear
|
||||
class="toolbar__search"
|
||||
placeholder="جستجوی نام یا کد"
|
||||
@search="
|
||||
() => {
|
||||
filters.page = 1;
|
||||
load();
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<a-popover
|
||||
v-model:open="filtersOpen"
|
||||
trigger="click"
|
||||
@@ -266,19 +333,6 @@ onBeforeUnmount(() => {
|
||||
</a-button>
|
||||
</a-badge>
|
||||
</a-popover>
|
||||
|
||||
<a-input-search
|
||||
v-model:value="filters.q"
|
||||
allow-clear
|
||||
class="toolbar__search"
|
||||
placeholder="جستجوی نام یا کد"
|
||||
@search="
|
||||
() => {
|
||||
filters.page = 1;
|
||||
load();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<RouterLink
|
||||
@@ -288,15 +342,87 @@ onBeforeUnmount(() => {
|
||||
query: filters.categoryId ? { categoryId: filters.categoryId } : {},
|
||||
}"
|
||||
>
|
||||
<a-button type="primary">
|
||||
<template #icon><VuesaxIcon name="add" :size="16" /></template>
|
||||
محصول جدید
|
||||
</a-button>
|
||||
<a-button type="primary">
|
||||
<template #icon><VuesaxIcon name="add" :size="16" /></template>
|
||||
محصول جدید
|
||||
</a-button>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Toolbar (Mobile Only) -->
|
||||
<div class="mobile-products-toolbar lg:!hidden flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2 w-full">
|
||||
<a-input-search
|
||||
v-model:value="filters.q"
|
||||
allow-clear
|
||||
class="flex-1"
|
||||
placeholder="جستجوی نام یا کد کالا..."
|
||||
@search="
|
||||
() => {
|
||||
filters.page = 1;
|
||||
load();
|
||||
}
|
||||
"
|
||||
/>
|
||||
<a-badge :count="activeFilterCount" :offset="[-2, 2]" color="#ff5a00">
|
||||
<a-button
|
||||
class="!h-9 !px-3 !rounded-lg !flex !items-center !gap-1.5"
|
||||
:type="activeFilterCount > 0 ? 'primary' : 'default'"
|
||||
:ghost="activeFilterCount > 0"
|
||||
@click="mobileFilterDrawer = true"
|
||||
>
|
||||
<template #icon><VuesaxIcon name="filter" :size="16" /></template>
|
||||
<span class="text-xs">فیلتر</span>
|
||||
</a-button>
|
||||
</a-badge>
|
||||
</div>
|
||||
|
||||
<!-- Active Filter Chips on Mobile -->
|
||||
<div
|
||||
v-if="activeFilterCount > 0 || filters.q"
|
||||
class="flex items-center gap-1.5 overflow-x-auto py-1 text-[11px] no-scrollbar"
|
||||
>
|
||||
<span
|
||||
v-if="filters.q"
|
||||
class="inline-flex items-center gap-1 bg-slate-100 text-slate-700 px-2 py-0.5 rounded-full shrink-0"
|
||||
>
|
||||
جستجو: «{{ filters.q }}»
|
||||
<button type="button" class="text-slate-400 hover:text-slate-600" @click="clearSearch">✕</button>
|
||||
</span>
|
||||
<span
|
||||
v-if="activeCategoryName"
|
||||
class="inline-flex items-center gap-1 bg-orange-50 text-orange-700 border border-orange-200/60 px-2 py-0.5 rounded-full shrink-0"
|
||||
>
|
||||
{{ activeCategoryName }}
|
||||
<button type="button" class="text-orange-400 hover:text-orange-700" @click="clearCategoryFilter">✕</button>
|
||||
</span>
|
||||
<span
|
||||
v-if="activeGroupName"
|
||||
class="inline-flex items-center gap-1 bg-blue-50 text-blue-700 border border-blue-200/60 px-2 py-0.5 rounded-full shrink-0"
|
||||
>
|
||||
{{ activeGroupName }}
|
||||
<button type="button" class="text-blue-400 hover:text-blue-700" @click="clearGroupFilter">✕</button>
|
||||
</span>
|
||||
<span
|
||||
v-if="filters.status"
|
||||
class="inline-flex items-center gap-1 bg-slate-100 text-slate-700 px-2 py-0.5 rounded-full shrink-0"
|
||||
>
|
||||
{{ filters.status === 'published' ? 'منتشرشده' : 'پیشنویس / مخفی' }}
|
||||
<button type="button" class="text-slate-400 hover:text-slate-700" @click="clearStatusFilter">✕</button>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="text-[10px] text-rose-500 font-medium hover:underline shrink-0 px-1"
|
||||
@click="clearAllFilters"
|
||||
>
|
||||
حذف همه
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div ref="tableShell" class="table-shell">
|
||||
<!-- Desktop Table View (lg and up) -->
|
||||
<div ref="tableShell" class="table-shell !hidden lg:!flex">
|
||||
<a-table
|
||||
class="table-shell__table"
|
||||
:columns="columns"
|
||||
@@ -366,6 +492,7 @@ onBeforeUnmount(() => {
|
||||
<template #icon><VuesaxIcon name="copy" :size="14" /></template>
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
v-if="auth.isAdmin"
|
||||
title="این محصول حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@@ -380,6 +507,269 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Product Cards View (Mobile Only) -->
|
||||
<div class="mobile-products-view block lg:!hidden pb-24">
|
||||
<!-- Reorder Info Banner -->
|
||||
<div
|
||||
v-if="reorderable"
|
||||
class="mb-3 p-2.5 rounded-xl bg-orange-50 border border-orange-200/70 text-xs text-orange-800 flex items-center gap-2"
|
||||
>
|
||||
<VuesaxIcon name="info-circle" :size="18" class="text-orange-600 shrink-0" />
|
||||
<span>حالت تغییر ترتیب فعال است. میتوانید با فلشها ترتیب کالاها را عوض کنید.</span>
|
||||
</div>
|
||||
|
||||
<!-- Count Header -->
|
||||
<div class="flex items-center justify-between text-xs text-slate-500 px-1 mb-2.5">
|
||||
<span>{{ total.toLocaleString("fa-IR") }} محصول</span>
|
||||
<span v-if="!reorderable && totalPages > 1" class="font-mono">
|
||||
صفحه {{ filters.page.toLocaleString("fa-IR") }} از {{ totalPages.toLocaleString("fa-IR") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-empty v-if="!rows.length && !loading" description="محصولی با این مشخصات یافت نشد" class="py-12" />
|
||||
|
||||
<div v-else class="space-y-2.5">
|
||||
<div
|
||||
v-for="(record, index) in rows"
|
||||
:key="record.id"
|
||||
class="bg-white rounded-xl border border-slate-200/80 p-3 shadow-2xs transition active:bg-slate-50/50"
|
||||
>
|
||||
<!-- Top: Thumbnail + Info -->
|
||||
<div class="flex items-start gap-3">
|
||||
<template v-if="thumbOf(record).url">
|
||||
<img
|
||||
:src="thumbOf(record).url!"
|
||||
alt=""
|
||||
:class="[
|
||||
'size-14 rounded-lg bg-slate-50 object-contain shrink-0 border border-slate-100',
|
||||
thumbOf(record).fallback && 'opacity-40 grayscale',
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="size-14 rounded-lg bg-slate-100 shrink-0 flex items-center justify-center text-slate-300">
|
||||
<VuesaxIcon name="gallery" :size="20" />
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<RouterLink
|
||||
:to="{ name: 'product-edit', params: { id: record.id } }"
|
||||
class="font-bold text-[13px] text-slate-800 hover:text-orange-600 line-clamp-2 leading-snug"
|
||||
>
|
||||
{{ record.name }}
|
||||
</RouterLink>
|
||||
<span class="text-[10px] font-mono text-slate-500 bg-slate-100 px-1.5 py-0.5 rounded shrink-0" dir="ltr">
|
||||
{{ record.code }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-1.5 mt-1.5 text-[11px] text-slate-500">
|
||||
<span class="text-slate-600 font-medium">{{ record.category.name }}</span>
|
||||
<span v-if="record.group" class="text-slate-400">/</span>
|
||||
<span v-if="record.group" class="text-slate-500">{{ record.group.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom: Publish Status Switch + Touch Actions -->
|
||||
<div class="flex items-center justify-between border-t border-slate-100 pt-2.5 mt-2.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<a-switch
|
||||
:checked="record.isPublished"
|
||||
size="small"
|
||||
@change="(v: unknown) => togglePublish(record, !!v)"
|
||||
/>
|
||||
<span class="text-xs" :class="record.isPublished ? 'text-emerald-600 font-medium' : 'text-slate-400'">
|
||||
{{ record.isPublished ? "منتشرشده" : "پیشنویس" }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<!-- Reorder buttons when reorderable -->
|
||||
<template v-if="reorderable">
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-600 bg-slate-100 hover:bg-slate-200 disabled:opacity-30 disabled:pointer-events-none active:bg-slate-300 transition"
|
||||
:disabled="index === 0"
|
||||
@click="move(index, -1)"
|
||||
>
|
||||
<VuesaxIcon name="arrow-up" :size="16" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-600 bg-slate-100 hover:bg-slate-200 disabled:opacity-30 disabled:pointer-events-none active:bg-slate-300 transition"
|
||||
:disabled="index === rows.length - 1"
|
||||
@click="move(index, 1)"
|
||||
>
|
||||
<VuesaxIcon name="arrow-down" :size="16" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- Edit Action -->
|
||||
<RouterLink :to="{ name: 'product-edit', params: { id: record.id } }">
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-600 bg-slate-100 hover:bg-slate-200 active:bg-slate-300 transition"
|
||||
title="ویرایش"
|
||||
>
|
||||
<VuesaxIcon name="edit-2" :size="16" />
|
||||
</button>
|
||||
</RouterLink>
|
||||
|
||||
<!-- Duplicate Action -->
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-slate-600 bg-slate-100 hover:bg-slate-200 active:bg-slate-300 transition"
|
||||
title="تکثیر / کپی"
|
||||
@click="duplicate(record)"
|
||||
>
|
||||
<VuesaxIcon name="copy" :size="16" />
|
||||
</button>
|
||||
|
||||
<!-- Delete Action -->
|
||||
<a-popconfirm
|
||||
v-if="auth.isAdmin"
|
||||
title="این محصول حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="size-8 rounded-lg flex items-center justify-center text-rose-500 bg-rose-50 hover:bg-rose-100 active:bg-rose-200 transition"
|
||||
title="حذف"
|
||||
>
|
||||
<VuesaxIcon name="trash" :size="16" />
|
||||
</button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Pagination -->
|
||||
<div v-if="!reorderable && totalPages > 1" class="flex items-center justify-between gap-3 pt-4 px-1">
|
||||
<a-button
|
||||
:disabled="filters.page <= 1"
|
||||
class="!flex-1 !h-10 !rounded-xl !font-medium"
|
||||
@click="onMobilePageChange(filters.page - 1)"
|
||||
>
|
||||
صفحه قبلی
|
||||
</a-button>
|
||||
<span class="text-xs text-slate-500 font-mono">
|
||||
{{ filters.page.toLocaleString("fa-IR") }} / {{ totalPages.toLocaleString("fa-IR") }}
|
||||
</span>
|
||||
<a-button
|
||||
:disabled="filters.page >= totalPages"
|
||||
class="!flex-1 !h-10 !rounded-xl !font-medium"
|
||||
@click="onMobilePageChange(filters.page + 1)"
|
||||
>
|
||||
صفحه بعدی
|
||||
</a-button>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
<!-- Mobile Floating Action Button (FAB) for New Product -->
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'product-new',
|
||||
query: filters.categoryId ? { categoryId: filters.categoryId } : {},
|
||||
}"
|
||||
class="fixed start-4 bottom-[72px] z-30 flex items-center gap-2 px-4 py-2.5 rounded-full bg-linear-to-r from-[#ff5a00] to-[#ff7a29] text-white font-bold text-xs shadow-lg shadow-orange-500/30 active:scale-95 transition"
|
||||
>
|
||||
<VuesaxIcon name="add" :size="18" />
|
||||
<span>محصول جدید</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Filter Bottom Sheet Drawer -->
|
||||
<a-drawer
|
||||
v-model:open="mobileFilterDrawer"
|
||||
placement="bottom"
|
||||
height="auto"
|
||||
:closable="false"
|
||||
class="mobile-filter-drawer"
|
||||
:body-style="{ padding: '16px 20px 24px', background: '#ffffff', borderTopLeftRadius: '20px', borderTopRightRadius: '20px' }"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between border-b border-slate-100 pb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<VuesaxIcon name="filter" :size="18" class="text-orange-600" />
|
||||
<span class="text-sm font-bold text-slate-800">فیلترهای کاتالوگ</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-slate-400 hover:text-slate-600"
|
||||
@click="mobileFilterDrawer = false"
|
||||
>
|
||||
بستن
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-700 mb-1.5">دستهبندی</label>
|
||||
<a-select
|
||||
v-model:value="filters.categoryId"
|
||||
allow-clear
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
placeholder="همه دستهها"
|
||||
:options="categories.map((c) => ({ value: c.id, label: c.name }))"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-700 mb-1.5">زیرگروه کالا</label>
|
||||
<a-select
|
||||
v-model:value="filters.groupId"
|
||||
allow-clear
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
placeholder="همه زیرگروهها"
|
||||
:disabled="!groups.length"
|
||||
:options="groups.map((g) => ({ value: g.id, label: g.name }))"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-700 mb-1.5">وضعیت انتشار</label>
|
||||
<a-select
|
||||
v-model:value="filters.status"
|
||||
allow-clear
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
placeholder="همه وضعیتها"
|
||||
:options="[
|
||||
{ value: 'published', label: 'منتشرشده در سایت' },
|
||||
{ value: 'draft', label: 'پیشنویس / مخفی' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2.5 pt-2 border-t border-slate-100">
|
||||
<a-button
|
||||
v-if="activeFilterCount > 0"
|
||||
danger
|
||||
class="!h-11 !rounded-xl"
|
||||
@click="clearAllFilters(); mobileFilterDrawer = false;"
|
||||
>
|
||||
حذف فیلترها
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
block
|
||||
class="!h-11 !rounded-xl !font-bold"
|
||||
@click="mobileFilterDrawer = false; load();"
|
||||
>
|
||||
اعمال فیلترها
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -32,24 +32,43 @@ async function save() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeader title="حساب کاربری" />
|
||||
<div class="grid max-w-4xl gap-6 md:grid-cols-2">
|
||||
<a-card title="مشخصات">
|
||||
<a-descriptions :column="1">
|
||||
<PageHeader title="حساب کاربری" subtitle="مشخصات فردی، نقش دسترسی و تغییر رمز عبور ورود به سامانه" />
|
||||
<div class="grid max-w-4xl gap-4 md:gap-6 md:grid-cols-2">
|
||||
<!-- User Identity Card -->
|
||||
<a-card title="مشخصات کاربری">
|
||||
<div class="flex items-center gap-3.5 mb-4 pb-4 border-b border-gray-100">
|
||||
<div class="flex size-14 items-center justify-center rounded-2xl bg-gradient-to-tr from-[#ff5a00] to-amber-500 text-xl font-black text-white shadow-md shadow-orange-500/20">
|
||||
{{ auth.user?.name?.slice(0, 1) || 'U' }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-base font-bold text-gray-900">{{ auth.user?.name }}</div>
|
||||
<div class="mt-0.5 text-xs font-mono text-gray-500" dir="ltr">{{ auth.user?.phone }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-descriptions :column="1" size="small">
|
||||
<a-descriptions-item label="نام">{{ auth.user?.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="موبایل"><span dir="ltr">{{ auth.user?.phone }}</span></a-descriptions-item>
|
||||
<a-descriptions-item label="نقش">{{ auth.user && ROLE_LABELS[auth.user.role] }}</a-descriptions-item>
|
||||
<a-descriptions-item label="موبایل"><span dir="ltr" class="font-mono">{{ auth.user?.phone }}</span></a-descriptions-item>
|
||||
<a-descriptions-item label="نقش دسترسی">
|
||||
<a-tag :color="auth.user?.role === 'ADMIN' ? 'orange' : 'blue'">
|
||||
{{ auth.user && ROLE_LABELS[auth.user.role] }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<!-- Change Password Card -->
|
||||
<a-card title="تغییر رمز عبور">
|
||||
<a-form layout="vertical" @submit.prevent="save">
|
||||
<FormField path="currentPassword" label="رمز فعلی" required>
|
||||
<a-input-password v-model:value="model.currentPassword" class="ltr-input" autocomplete="current-password" />
|
||||
<a-input-password v-model:value="model.currentPassword" class="ltr-input h-10" autocomplete="current-password" />
|
||||
</FormField>
|
||||
<FormField path="newPassword" label="رمز جدید" hint="حداقل ۸ کاراکتر" required>
|
||||
<a-input-password v-model:value="model.newPassword" class="ltr-input" autocomplete="new-password" />
|
||||
<a-input-password v-model:value="model.newPassword" class="ltr-input h-10" autocomplete="new-password" />
|
||||
</FormField>
|
||||
<a-button type="primary" html-type="submit" :loading="saving">تغییر رمز</a-button>
|
||||
<a-button type="primary" html-type="submit" :loading="saving" class="w-full md:w-auto h-10 !font-bold">
|
||||
تغییر رمز ورود
|
||||
</a-button>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
import type { SectionResponse } from "@/api/types";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useZodForm } from "@/composables/useZodForm";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { jalali, SITE_URL } from "@/utils/format";
|
||||
import AboutForm from "@/sections/AboutForm.vue";
|
||||
import ContactForm from "@/sections/ContactForm.vue";
|
||||
@@ -37,6 +38,7 @@ const FORMS: Record<SectionKey, Component> = {
|
||||
footer: FooterForm,
|
||||
};
|
||||
|
||||
const auth = useAuthStore();
|
||||
const route = useRoute();
|
||||
const key = String(route.params.key);
|
||||
const valid = isSectionKey(key);
|
||||
@@ -55,7 +57,11 @@ const label = computed(() => (valid ? SECTION_LABELS[sectionKey] : null));
|
||||
async function load() {
|
||||
try {
|
||||
const res = await api<SectionResponse>(`/admin/sections/${sectionKey}`);
|
||||
reset(res.data as never);
|
||||
const data = res.data as any;
|
||||
if (sectionKey !== "settings" && data.enabled === undefined) {
|
||||
data.enabled = true;
|
||||
}
|
||||
reset(data as never);
|
||||
updatedAt.value = res.updatedAt;
|
||||
loaded.value = true;
|
||||
} catch (e) {
|
||||
@@ -79,6 +85,33 @@ async function save() {
|
||||
}
|
||||
}
|
||||
|
||||
const togglingVisibility = ref(false);
|
||||
|
||||
async function onVisibilityChange(val: any) {
|
||||
const nextVal = Boolean(val);
|
||||
(model.value as any).enabled = nextVal;
|
||||
togglingVisibility.value = true;
|
||||
try {
|
||||
const res = await api<{ key: string; enabled: boolean; updatedAt: string }>(
|
||||
`/admin/sections/${sectionKey}/visibility`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: { enabled: nextVal },
|
||||
},
|
||||
);
|
||||
(model.value as any).enabled = res.enabled !== false;
|
||||
updatedAt.value = res.updatedAt;
|
||||
void message.success(
|
||||
`وضعیت نمایش به ${nextVal ? "نمایش" : "عدم نمایش"} تغییر یافت`,
|
||||
);
|
||||
} catch (e) {
|
||||
(model.value as any).enabled = !nextVal;
|
||||
void message.error(errorMessage(e));
|
||||
} finally {
|
||||
togglingVisibility.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) void load();
|
||||
</script>
|
||||
|
||||
@@ -90,16 +123,81 @@ if (valid) void load();
|
||||
:subtitle="updatedAt ? `آخرین ذخیره: ${jalali(updatedAt)}` : label!.desc"
|
||||
back="/sections"
|
||||
>
|
||||
<a :href="SITE_URL" target="_blank" rel="noopener"><a-button>مشاهده سایت</a-button></a>
|
||||
<a-button type="primary" :loading="saving" :disabled="!loaded" @click="save">
|
||||
<div v-if="sectionKey !== 'settings'" class="max-lg:hidden flex items-center gap-2 me-2">
|
||||
<span class="text-sm font-medium text-gray-700">نمایش در سایت:</span>
|
||||
<a-switch
|
||||
:checked="(model as any)?.enabled !== false"
|
||||
:loading="togglingVisibility"
|
||||
checked-children="نمایش"
|
||||
un-checked-children="مخفی"
|
||||
@change="onVisibilityChange"
|
||||
/>
|
||||
</div>
|
||||
<a :href="SITE_URL" target="_blank" rel="noopener" class="max-lg:hidden"><a-button>مشاهده سایت</a-button></a>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="max-lg:hidden"
|
||||
:loading="saving"
|
||||
:disabled="!loaded || (sectionKey === 'settings' && !auth.isAdmin)"
|
||||
@click="save"
|
||||
>
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
ذخیره
|
||||
</a-button>
|
||||
</PageHeader>
|
||||
<a-spin :spinning="!loaded">
|
||||
<a-form v-if="loaded" layout="vertical" @submit.prevent="save">
|
||||
<a-alert
|
||||
v-if="loaded && sectionKey === 'settings' && !auth.isAdmin"
|
||||
type="error"
|
||||
show-icon
|
||||
message="فقط مدیر کل میتواند تنظیمات سایت را تغییر دهد."
|
||||
class="mb-4"
|
||||
/>
|
||||
<a-alert
|
||||
v-else-if="loaded && sectionKey !== 'settings' && (model as any).enabled === false"
|
||||
type="warning"
|
||||
show-icon
|
||||
message="این بخش در حال حاضر در سایت نمایش داده نمیشود (مخفی است)."
|
||||
class="mb-4"
|
||||
/>
|
||||
<a-form v-if="loaded" layout="vertical" class="pb-20 lg:pb-0" @submit.prevent="save">
|
||||
<component :is="FORMS[sectionKey]" v-model="model" />
|
||||
</a-form>
|
||||
</a-spin>
|
||||
|
||||
<!-- Mobile Sticky Bottom Action Bar -->
|
||||
<div class="lg:hidden fixed bottom-0 inset-x-0 z-30 border-t border-gray-200 bg-white/95 px-4 py-2.5 backdrop-blur-md shadow-lg flex items-center justify-between gap-3">
|
||||
<div v-if="sectionKey !== 'settings'" class="flex items-center gap-2">
|
||||
<a-switch
|
||||
:checked="(model as any)?.enabled !== false"
|
||||
:loading="togglingVisibility"
|
||||
size="small"
|
||||
@change="onVisibilityChange"
|
||||
/>
|
||||
<span class="text-xs font-medium text-gray-700">
|
||||
{{ (model as any)?.enabled !== false ? 'نمایش در سایت' : 'مخفی' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="text-xs text-gray-500 font-medium">تنظیمات پایه</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<a :href="SITE_URL" target="_blank" rel="noopener">
|
||||
<a-button size="middle" class="!px-3" title="مشاهده سایت">
|
||||
<template #icon><VuesaxIcon name="eye" :size="16" /></template>
|
||||
</a-button>
|
||||
</a>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="middle"
|
||||
class="!font-bold !px-5"
|
||||
:loading="saving"
|
||||
:disabled="!loaded || (sectionKey === 'settings' && !auth.isAdmin)"
|
||||
@click="save"
|
||||
>
|
||||
<template #icon><VuesaxIcon name="save-2" :size="16" /></template>
|
||||
ذخیره تغییرات
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -1,24 +1,158 @@
|
||||
<script setup lang="ts">
|
||||
import { SECTION_KEYS, SECTION_LABELS } from "@imen/shared";
|
||||
import { SECTION_KEYS, SECTION_LABELS, type SectionKey } from "@imen/shared";
|
||||
import { message } from "ant-design-vue";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { api, errorMessage } from "@/api/http";
|
||||
import type { AdminSectionSummary } from "@/api/types";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { jalali } from "@/utils/format";
|
||||
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
// site-wide settings are editable by admins only
|
||||
const keys = SECTION_KEYS.filter((k) => k !== "settings" || auth.isAdmin);
|
||||
|
||||
const loading = ref(true);
|
||||
const sectionEnabled = reactive<Record<string, boolean>>({
|
||||
hero: true,
|
||||
tape: true,
|
||||
stats: true,
|
||||
productsIntro: true,
|
||||
about: true,
|
||||
services: true,
|
||||
standards: true,
|
||||
industries: true,
|
||||
testimonials: true,
|
||||
contact: true,
|
||||
footer: true,
|
||||
});
|
||||
const sectionUpdatedAt = reactive<Record<string, string | null>>({});
|
||||
const toggling = reactive<Record<string, boolean>>({});
|
||||
|
||||
function isEnabled(key: SectionKey): boolean {
|
||||
return sectionEnabled[key] !== false;
|
||||
}
|
||||
|
||||
function getUpdatedAt(key: SectionKey): string | null {
|
||||
return sectionUpdatedAt[key] ?? null;
|
||||
}
|
||||
|
||||
async function fetchSections() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const list = await api<AdminSectionSummary[]>("/admin/sections");
|
||||
for (const item of list) {
|
||||
sectionEnabled[item.key] = item.enabled !== false;
|
||||
sectionUpdatedAt[item.key] = item.updatedAt;
|
||||
}
|
||||
} catch (e) {
|
||||
void message.error(errorMessage(e));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggle(key: SectionKey, nextVal: boolean) {
|
||||
if (key === "settings") return;
|
||||
const prevVal = sectionEnabled[key] !== false;
|
||||
sectionEnabled[key] = nextVal;
|
||||
toggling[key] = true;
|
||||
|
||||
try {
|
||||
const res = await api<{ key: string; enabled: boolean; updatedAt: string }>(
|
||||
`/admin/sections/${key}/visibility`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: { enabled: nextVal },
|
||||
},
|
||||
);
|
||||
sectionEnabled[key] = res.enabled !== false;
|
||||
sectionUpdatedAt[key] = res.updatedAt;
|
||||
void message.success(
|
||||
`وضعیت «${SECTION_LABELS[key].title}» به ${nextVal ? "نمایش" : "عدم نمایش"} تغییر یافت`,
|
||||
);
|
||||
} catch (e) {
|
||||
sectionEnabled[key] = prevVal;
|
||||
void message.error(errorMessage(e));
|
||||
} finally {
|
||||
toggling[key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
function goToEdit(key: SectionKey) {
|
||||
void router.push({ name: "section-edit", params: { key } });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void fetchSections();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeader
|
||||
title="بخشهای سایت"
|
||||
subtitle="متنها، تصاویر و فهرستهای صفحه اصلی؛ تغییرات پس از ذخیره حداکثر ظرف یک دقیقه روی سایت دیده میشوند"
|
||||
subtitle="متنها، تصاویر و فهرستهای صفحه اصلی؛ تنظیم وضعیت نمایش و ویرایش محتوا"
|
||||
/>
|
||||
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<RouterLink v-for="key in keys" :key="key" :to="{ name: 'section-edit', params: { key } }">
|
||||
<a-card hoverable class="h-full">
|
||||
<div class="font-bold text-gray-800">{{ SECTION_LABELS[key].title }}</div>
|
||||
<p class="mt-2 text-sm leading-6 text-gray-500">{{ SECTION_LABELS[key].desc }}</p>
|
||||
</a-card>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<div
|
||||
v-for="key in keys"
|
||||
:key="key"
|
||||
class="block group"
|
||||
>
|
||||
<a-card
|
||||
hoverable
|
||||
class="h-full flex flex-col justify-between transition-all"
|
||||
:class="{ 'opacity-75 bg-gray-50/60': key !== 'settings' && !isEnabled(key) }"
|
||||
>
|
||||
<div>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div
|
||||
class="flex items-center gap-2 cursor-pointer flex-1"
|
||||
@click="goToEdit(key)"
|
||||
>
|
||||
<span class="font-bold text-gray-800 group-hover:text-orange-600 transition-colors">
|
||||
{{ SECTION_LABELS[key].title }}
|
||||
</span>
|
||||
<a-tag v-if="key === 'settings'" color="blue">تنظیمات پایه</a-tag>
|
||||
<a-tag v-else-if="!isEnabled(key)" color="error">مخفی</a-tag>
|
||||
</div>
|
||||
|
||||
<!-- Switch toggle: standalone button without any outer anchor tag -->
|
||||
<div
|
||||
v-if="key !== 'settings'"
|
||||
class="flex items-center flex-shrink-0"
|
||||
>
|
||||
<a-switch
|
||||
:checked="isEnabled(key)"
|
||||
:loading="toggling[key]"
|
||||
checked-children="نمایش"
|
||||
un-checked-children="مخفی"
|
||||
@change="(val: any) => toggle(key, Boolean(val))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="mt-2 text-sm leading-6 text-gray-500 cursor-pointer"
|
||||
@click="goToEdit(key)"
|
||||
>
|
||||
{{ SECTION_LABELS[key].desc }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-4 flex items-center justify-between border-t border-gray-100 pt-3 text-xs text-gray-400 cursor-pointer"
|
||||
@click="goToEdit(key)"
|
||||
>
|
||||
<span class="text-primary font-medium group-hover:underline">ویرایش بخش ←</span>
|
||||
<span v-if="getUpdatedAt(key)">آخرین تغییر: {{ jalali(getUpdatedAt(key)) }}</span>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
@@ -110,7 +110,8 @@ onMounted(load);
|
||||
</a-button>
|
||||
</PageHeader>
|
||||
|
||||
<a-card :body-style="{ padding: 0 }">
|
||||
<!-- Desktop Table (Preserved for lg+) -->
|
||||
<a-card :body-style="{ padding: 0 }" class="max-lg:hidden">
|
||||
<a-table :columns="columns" :data-source="rows" :loading="loading" row-key="id" :pagination="false" :scroll="{ x: 700 }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'phone'"><span dir="ltr">{{ record.phone }}</span></template>
|
||||
@@ -139,6 +140,155 @@ onMounted(load);
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- Mobile User Cards (App Experience for <lg) -->
|
||||
<div class="lg:hidden space-y-3">
|
||||
<a-spin :spinning="loading">
|
||||
<div v-if="!rows.length && !loading" class="rounded-xl border border-dashed border-gray-200 bg-white p-8 text-center">
|
||||
<div class="text-sm text-gray-500">کاربری یافت نشد</div>
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="user in rows"
|
||||
:key="user.id"
|
||||
class="rounded-xl border border-gray-100 bg-white p-4 shadow-sm transition-all"
|
||||
>
|
||||
<!-- Header: User Avatar & Role Tag -->
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex size-11 flex-shrink-0 items-center justify-center rounded-full font-bold text-white shadow-sm"
|
||||
:class="user.role === 'ADMIN' ? 'bg-gradient-to-tr from-orange-600 to-amber-500' : 'bg-gradient-to-tr from-blue-600 to-sky-500'"
|
||||
>
|
||||
{{ user.name.slice(0, 1) }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-gray-900">{{ user.name }}</div>
|
||||
<a
|
||||
:href="`tel:${user.phone}`"
|
||||
class="mt-0.5 inline-block text-xs font-mono text-gray-500 hover:text-orange-600"
|
||||
dir="ltr"
|
||||
>
|
||||
{{ user.phone }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-end gap-1">
|
||||
<a-tag :color="user.role === 'ADMIN' ? 'orange' : 'blue'" class="!m-0 text-[11px]">
|
||||
{{ ROLE_LABELS[user.role as keyof typeof ROLE_LABELS] }}
|
||||
</a-tag>
|
||||
<a-tag :color="user.isActive ? 'green' : 'default'" class="!m-0 text-[10px]">
|
||||
{{ user.isActive ? "فعال" : "غیرفعال" }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Meta & Actions -->
|
||||
<div class="mt-3.5 flex items-center justify-between border-t border-gray-100 pt-3 text-xs text-gray-400">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span>آخرین ورود:</span>
|
||||
<span class="font-medium text-gray-600">{{ jalali(user.lastLoginAt) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<a-button size="small" class="!flex !items-center !gap-1" @click="openEdit(user)">
|
||||
<template #icon><VuesaxIcon name="edit-2" :size="14" /></template>
|
||||
<span>ویرایش</span>
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
v-if="user.id !== auth.user?.id"
|
||||
title="این کاربر حذف شود؟"
|
||||
ok-text="حذف"
|
||||
cancel-text="انصراف"
|
||||
@confirm="remove(user)"
|
||||
>
|
||||
<a-button size="small" danger class="!px-2">
|
||||
<template #icon><VuesaxIcon name="trash" :size="14" /></template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Floating Action Button (FAB) for New User -->
|
||||
<button
|
||||
type="button"
|
||||
class="lg:hidden fixed bottom-20 left-4 z-30 flex size-13 items-center justify-center rounded-full bg-[#ff5a00] text-white shadow-lg shadow-orange-500/30 transition-transform active:scale-95"
|
||||
title="کاربر جدید"
|
||||
@click="openNew"
|
||||
>
|
||||
<VuesaxIcon name="add" :size="24" />
|
||||
</button>
|
||||
|
||||
<!-- Mobile Collapsible Role Permissions Guide -->
|
||||
<details class="lg:hidden mt-4 rounded-xl border border-gray-200 bg-white p-3 text-xs shadow-sm">
|
||||
<summary class="cursor-pointer font-bold text-gray-700 flex items-center justify-between select-none">
|
||||
<span class="flex items-center gap-1.5">
|
||||
<VuesaxIcon name="info-circle" :size="16" class="text-orange-500" />
|
||||
راهنمای نقشها و مجوزهای دسترسی
|
||||
</span>
|
||||
<span class="text-gray-400 text-[10px]">لمس برای مشاهده</span>
|
||||
</summary>
|
||||
<div class="mt-3 space-y-3 pt-3 border-t border-gray-100">
|
||||
<div class="rounded-lg border border-orange-200 bg-orange-50/40 p-2.5">
|
||||
<div class="flex items-center gap-2 font-bold text-orange-700">
|
||||
<a-tag color="orange">مدیر کل (ADMIN)</a-tag>
|
||||
<span class="text-[11px]">دسترسی ارشد سیستمی</span>
|
||||
</div>
|
||||
<ul class="mt-2 space-y-1 text-[11px] text-gray-600 list-disc list-inside">
|
||||
<li>مدیریت کاربران، نقشها و تنظیمات امنیتی</li>
|
||||
<li>تنظیمات پایه سایت، سئو، شبکهها و ساعات کاری</li>
|
||||
<li>حذف دادهها (محصولات، دستهها، پیامها، رسانهها)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-blue-200 bg-blue-50/40 p-2.5">
|
||||
<div class="flex items-center gap-2 font-bold text-blue-700">
|
||||
<a-tag color="blue">ویرایشگر (EDITOR)</a-tag>
|
||||
<span class="text-[11px]">دسترسی محتوایی</span>
|
||||
</div>
|
||||
<ul class="mt-2 space-y-1 text-[11px] text-gray-600 list-disc list-inside">
|
||||
<li>ایجاد، ویرایش و انتشار محصولات و دستهبندیها</li>
|
||||
<li>ویرایش محتوای بخشهای صفحه اصلی</li>
|
||||
<li>آپلود تصاویر در رسانه و مشاهده پیامهای تماس</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Desktop Role Permissions Reference Cards -->
|
||||
<div class="mt-6 max-lg:hidden grid gap-4 md:grid-cols-2">
|
||||
<a-card size="small" class="border-orange-200 bg-orange-50/30">
|
||||
<div class="flex items-center gap-2 font-bold text-orange-700">
|
||||
<a-tag color="orange">مدیر کل (ADMIN)</a-tag>
|
||||
<span class="text-xs">دسترسی ارشد سیستمی</span>
|
||||
</div>
|
||||
<ul class="mt-2.5 space-y-1.5 text-xs text-gray-600 list-disc list-inside">
|
||||
<li>مدیریت کاربران، افزودن کاربر و تغییر نقشها و وضعیت حساب</li>
|
||||
<li>دسترسی کامل به تنظیمات پایه سایت، ساعات کاری، شبکهها و سئو</li>
|
||||
<li>مجوز حذف محصولات، دستهبندیها، رسانهها و پیامهای تماس</li>
|
||||
<li>مشاهده لاگهای امنیتی و رویدادهای کامل سامانه</li>
|
||||
</ul>
|
||||
</a-card>
|
||||
|
||||
<a-card size="small" class="border-blue-200 bg-blue-50/30">
|
||||
<div class="flex items-center gap-2 font-bold text-blue-700">
|
||||
<a-tag color="blue">ویرایشگر (EDITOR)</a-tag>
|
||||
<span class="text-xs">دسترسی محتوایی</span>
|
||||
</div>
|
||||
<ul class="mt-2.5 space-y-1.5 text-xs text-gray-600 list-disc list-inside">
|
||||
<li>ایجاد، ویرایش، کپی و انتشار محصولات و گروهها</li>
|
||||
<li>ویرایش محتوای بخشهای صفحه اصلی و فعال/مخفیسازی نمایش آنها</li>
|
||||
<li>آپلود و ویرایش اطلاعات رسانهها در کتابخانه رسانه</li>
|
||||
<li>مشاهده پیامهای تماس و بهروزرسانی وضعیت (بدون دسترسی حذف یا مدیریت کاربران)</li>
|
||||
</ul>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<a-modal
|
||||
:open="editing !== null"
|
||||
:title="isNew ? 'کاربر جدید' : 'ویرایش کاربر'"
|
||||
@@ -168,6 +318,9 @@ onMounted(load);
|
||||
:disabled="editing === auth.user?.id"
|
||||
:options="ROLE_OPTIONS"
|
||||
/>
|
||||
<div class="mt-1.5 text-[11px] text-gray-500">
|
||||
{{ model.role === 'ADMIN' ? 'مدیر کل: دسترسی کامل سیستمی، حذف دادهها، تنظیمات و مدیریت کاربران' : 'ویرایشگر: ویرایش محتوا، محصولات، بخشها و پیامها (بدون حذف)' }}
|
||||
</div>
|
||||
</FormField>
|
||||
<FormField v-if="!isNew" path="isActive">
|
||||
<a-checkbox v-model:checked="model.isActive" :disabled="editing === auth.user?.id">حساب فعال است</a-checkbox>
|
||||
|
||||
@@ -39,3 +39,72 @@ textarea.ltr-input {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Remove borders from all icon buttons and action buttons across the admin panel */
|
||||
.ant-btn-icon-only,
|
||||
.ant-btn.ant-btn-icon-only,
|
||||
.ant-table .ant-btn:not(.ant-btn-primary),
|
||||
.ant-space .ant-btn:not(.ant-btn-primary),
|
||||
.ant-btn:has(> .vx-icon:only-child):not(.ant-btn-primary),
|
||||
.ant-btn:has(> span > .vx-icon:only-child):not(.ant-btn-primary) {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.ant-btn-icon-only:not(.ant-btn-primary):not(:hover),
|
||||
.ant-table .ant-btn:not(.ant-btn-primary):not(:hover),
|
||||
.ant-space .ant-btn:not(.ant-btn-primary):not(:hover) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-btn-icon-only:not(.ant-btn-primary):hover,
|
||||
.ant-table .ant-btn:not(.ant-btn-primary):hover,
|
||||
.ant-space .ant-btn:not(.ant-btn-primary):hover {
|
||||
border: none !important;
|
||||
background: #f1f5f9 !important;
|
||||
}
|
||||
|
||||
.ant-btn-icon-only.ant-btn-dangerous,
|
||||
.ant-table .ant-btn.ant-btn-dangerous,
|
||||
.ant-space .ant-btn.ant-btn-dangerous {
|
||||
border: none !important;
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
|
||||
.ant-btn-icon-only.ant-btn-dangerous:hover,
|
||||
.ant-table .ant-btn.ant-btn-dangerous:hover,
|
||||
.ant-space .ant-btn.ant-btn-dangerous:hover {
|
||||
border: none !important;
|
||||
background: #fee2e2 !important;
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
|
||||
/* Ensure proper spacing and optical vertical alignment for icons in buttons across the admin */
|
||||
.ant-btn {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.ant-btn > .anticon,
|
||||
.ant-btn > .vx-icon,
|
||||
.ant-btn > .ant-btn-icon {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
line-height: 0 !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.ant-btn-icon-only {
|
||||
gap: 0 !important;
|
||||
}
|
||||
|
||||
.ant-btn-icon-only > .vx-icon,
|
||||
.ant-btn-icon-only > .anticon,
|
||||
.ant-btn-icon-only > .ant-btn-icon {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ NODE_ENV=development
|
||||
CORS_ORIGINS=http://localhost:5173,http://localhost:3000
|
||||
|
||||
# --- database (docker-compose.dev.yml) ---
|
||||
DATABASE_URL=postgresql://imen:imen@localhost:5433/imen
|
||||
DATABASE_URL=postgresql://imen:imen@localhost:5435/imen
|
||||
|
||||
# --- auth ---
|
||||
# generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
|
||||
|
||||
+9040
File diff suppressed because it is too large
Load Diff
+129
@@ -0,0 +1,129 @@
|
||||
import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
|
||||
import {
|
||||
__commonJS,
|
||||
__name
|
||||
} from "./chunk-T6GW54FI.js";
|
||||
|
||||
// ../../node_modules/.pnpm/ms@2.1.3/node_modules/ms/index.js
|
||||
var require_ms = __commonJS({
|
||||
"../../node_modules/.pnpm/ms@2.1.3/node_modules/ms/index.js"(exports, module) {
|
||||
var s = 1e3;
|
||||
var m = s * 60;
|
||||
var h = m * 60;
|
||||
var d = h * 24;
|
||||
var w = d * 7;
|
||||
var y = d * 365.25;
|
||||
module.exports = function(val, options) {
|
||||
options = options || {};
|
||||
var type = typeof val;
|
||||
if (type === "string" && val.length > 0) {
|
||||
return parse(val);
|
||||
} else if (type === "number" && isFinite(val)) {
|
||||
return options.long ? fmtLong(val) : fmtShort(val);
|
||||
}
|
||||
throw new Error(
|
||||
"val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
|
||||
);
|
||||
};
|
||||
function parse(str) {
|
||||
str = String(str);
|
||||
if (str.length > 100) {
|
||||
return;
|
||||
}
|
||||
var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
|
||||
str
|
||||
);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
var n = parseFloat(match[1]);
|
||||
var type = (match[2] || "ms").toLowerCase();
|
||||
switch (type) {
|
||||
case "years":
|
||||
case "year":
|
||||
case "yrs":
|
||||
case "yr":
|
||||
case "y":
|
||||
return n * y;
|
||||
case "weeks":
|
||||
case "week":
|
||||
case "w":
|
||||
return n * w;
|
||||
case "days":
|
||||
case "day":
|
||||
case "d":
|
||||
return n * d;
|
||||
case "hours":
|
||||
case "hour":
|
||||
case "hrs":
|
||||
case "hr":
|
||||
case "h":
|
||||
return n * h;
|
||||
case "minutes":
|
||||
case "minute":
|
||||
case "mins":
|
||||
case "min":
|
||||
case "m":
|
||||
return n * m;
|
||||
case "seconds":
|
||||
case "second":
|
||||
case "secs":
|
||||
case "sec":
|
||||
case "s":
|
||||
return n * s;
|
||||
case "milliseconds":
|
||||
case "millisecond":
|
||||
case "msecs":
|
||||
case "msec":
|
||||
case "ms":
|
||||
return n;
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
__name(parse, "parse");
|
||||
function fmtShort(ms) {
|
||||
var msAbs = Math.abs(ms);
|
||||
if (msAbs >= d) {
|
||||
return Math.round(ms / d) + "d";
|
||||
}
|
||||
if (msAbs >= h) {
|
||||
return Math.round(ms / h) + "h";
|
||||
}
|
||||
if (msAbs >= m) {
|
||||
return Math.round(ms / m) + "m";
|
||||
}
|
||||
if (msAbs >= s) {
|
||||
return Math.round(ms / s) + "s";
|
||||
}
|
||||
return ms + "ms";
|
||||
}
|
||||
__name(fmtShort, "fmtShort");
|
||||
function fmtLong(ms) {
|
||||
var msAbs = Math.abs(ms);
|
||||
if (msAbs >= d) {
|
||||
return plural(ms, msAbs, d, "day");
|
||||
}
|
||||
if (msAbs >= h) {
|
||||
return plural(ms, msAbs, h, "hour");
|
||||
}
|
||||
if (msAbs >= m) {
|
||||
return plural(ms, msAbs, m, "minute");
|
||||
}
|
||||
if (msAbs >= s) {
|
||||
return plural(ms, msAbs, s, "second");
|
||||
}
|
||||
return ms + " ms";
|
||||
}
|
||||
__name(fmtLong, "fmtLong");
|
||||
function plural(ms, msAbs, n, name) {
|
||||
var isPlural = msAbs >= n * 1.5;
|
||||
return Math.round(ms / n) + " " + name + (isPlural ? "s" : "");
|
||||
}
|
||||
__name(plural, "plural");
|
||||
}
|
||||
});
|
||||
|
||||
export {
|
||||
require_ms
|
||||
};
|
||||
+15727
File diff suppressed because it is too large
Load Diff
+725
@@ -0,0 +1,725 @@
|
||||
import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
|
||||
import {
|
||||
require_ms
|
||||
} from "./chunk-A34KYKJP.js";
|
||||
import {
|
||||
__commonJS,
|
||||
__esm,
|
||||
__export,
|
||||
__name,
|
||||
__require,
|
||||
__toCommonJS
|
||||
} from "./chunk-T6GW54FI.js";
|
||||
|
||||
// ../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/common.js
|
||||
var require_common = __commonJS({
|
||||
"../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/common.js"(exports, module) {
|
||||
function setup(env2) {
|
||||
createDebug.debug = createDebug;
|
||||
createDebug.default = createDebug;
|
||||
createDebug.coerce = coerce;
|
||||
createDebug.disable = disable;
|
||||
createDebug.enable = enable;
|
||||
createDebug.enabled = enabled;
|
||||
createDebug.humanize = require_ms();
|
||||
createDebug.destroy = destroy;
|
||||
Object.keys(env2).forEach((key) => {
|
||||
createDebug[key] = env2[key];
|
||||
});
|
||||
createDebug.names = [];
|
||||
createDebug.skips = [];
|
||||
createDebug.formatters = {};
|
||||
function selectColor(namespace) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < namespace.length; i++) {
|
||||
hash = (hash << 5) - hash + namespace.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
|
||||
}
|
||||
__name(selectColor, "selectColor");
|
||||
createDebug.selectColor = selectColor;
|
||||
function createDebug(namespace) {
|
||||
let prevTime;
|
||||
let enableOverride = null;
|
||||
let namespacesCache;
|
||||
let enabledCache;
|
||||
function debug(...args) {
|
||||
if (!debug.enabled) {
|
||||
return;
|
||||
}
|
||||
const self = debug;
|
||||
const curr = Number(/* @__PURE__ */ new Date());
|
||||
const ms = curr - (prevTime || curr);
|
||||
self.diff = ms;
|
||||
self.prev = prevTime;
|
||||
self.curr = curr;
|
||||
prevTime = curr;
|
||||
args[0] = createDebug.coerce(args[0]);
|
||||
if (typeof args[0] !== "string") {
|
||||
args.unshift("%O");
|
||||
}
|
||||
let index = 0;
|
||||
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
|
||||
if (match === "%%") {
|
||||
return "%";
|
||||
}
|
||||
index++;
|
||||
const formatter = createDebug.formatters[format];
|
||||
if (typeof formatter === "function") {
|
||||
const val = args[index];
|
||||
match = formatter.call(self, val);
|
||||
args.splice(index, 1);
|
||||
index--;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
createDebug.formatArgs.call(self, args);
|
||||
const logFn = self.log || createDebug.log;
|
||||
logFn.apply(self, args);
|
||||
}
|
||||
__name(debug, "debug");
|
||||
debug.namespace = namespace;
|
||||
debug.useColors = createDebug.useColors();
|
||||
debug.color = createDebug.selectColor(namespace);
|
||||
debug.extend = extend;
|
||||
debug.destroy = createDebug.destroy;
|
||||
Object.defineProperty(debug, "enabled", {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
get: /* @__PURE__ */ __name(() => {
|
||||
if (enableOverride !== null) {
|
||||
return enableOverride;
|
||||
}
|
||||
if (namespacesCache !== createDebug.namespaces) {
|
||||
namespacesCache = createDebug.namespaces;
|
||||
enabledCache = createDebug.enabled(namespace);
|
||||
}
|
||||
return enabledCache;
|
||||
}, "get"),
|
||||
set: /* @__PURE__ */ __name((v) => {
|
||||
enableOverride = v;
|
||||
}, "set")
|
||||
});
|
||||
if (typeof createDebug.init === "function") {
|
||||
createDebug.init(debug);
|
||||
}
|
||||
return debug;
|
||||
}
|
||||
__name(createDebug, "createDebug");
|
||||
function extend(namespace, delimiter) {
|
||||
const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace);
|
||||
newDebug.log = this.log;
|
||||
return newDebug;
|
||||
}
|
||||
__name(extend, "extend");
|
||||
function enable(namespaces) {
|
||||
createDebug.save(namespaces);
|
||||
createDebug.namespaces = namespaces;
|
||||
createDebug.names = [];
|
||||
createDebug.skips = [];
|
||||
const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(/\s+/g, ",").split(",").filter(Boolean);
|
||||
for (const ns of split) {
|
||||
if (ns[0] === "-") {
|
||||
createDebug.skips.push(ns.slice(1));
|
||||
} else {
|
||||
createDebug.names.push(ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
__name(enable, "enable");
|
||||
function matchesTemplate(search, template) {
|
||||
let searchIndex = 0;
|
||||
let templateIndex = 0;
|
||||
let starIndex = -1;
|
||||
let matchIndex = 0;
|
||||
while (searchIndex < search.length) {
|
||||
if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
|
||||
if (template[templateIndex] === "*") {
|
||||
starIndex = templateIndex;
|
||||
matchIndex = searchIndex;
|
||||
templateIndex++;
|
||||
} else {
|
||||
searchIndex++;
|
||||
templateIndex++;
|
||||
}
|
||||
} else if (starIndex !== -1) {
|
||||
templateIndex = starIndex + 1;
|
||||
matchIndex++;
|
||||
searchIndex = matchIndex;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while (templateIndex < template.length && template[templateIndex] === "*") {
|
||||
templateIndex++;
|
||||
}
|
||||
return templateIndex === template.length;
|
||||
}
|
||||
__name(matchesTemplate, "matchesTemplate");
|
||||
function disable() {
|
||||
const namespaces = [
|
||||
...createDebug.names,
|
||||
...createDebug.skips.map((namespace) => "-" + namespace)
|
||||
].join(",");
|
||||
createDebug.enable("");
|
||||
return namespaces;
|
||||
}
|
||||
__name(disable, "disable");
|
||||
function enabled(name) {
|
||||
for (const skip of createDebug.skips) {
|
||||
if (matchesTemplate(name, skip)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const ns of createDebug.names) {
|
||||
if (matchesTemplate(name, ns)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
__name(enabled, "enabled");
|
||||
function coerce(val) {
|
||||
if (val instanceof Error) {
|
||||
return val.stack || val.message;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
__name(coerce, "coerce");
|
||||
function destroy() {
|
||||
console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
|
||||
}
|
||||
__name(destroy, "destroy");
|
||||
createDebug.enable(createDebug.load());
|
||||
return createDebug;
|
||||
}
|
||||
__name(setup, "setup");
|
||||
module.exports = setup;
|
||||
}
|
||||
});
|
||||
|
||||
// ../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/browser.js
|
||||
var require_browser = __commonJS({
|
||||
"../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/browser.js"(exports, module) {
|
||||
exports.formatArgs = formatArgs;
|
||||
exports.save = save;
|
||||
exports.load = load;
|
||||
exports.useColors = useColors;
|
||||
exports.storage = localstorage();
|
||||
exports.destroy = /* @__PURE__ */ (() => {
|
||||
let warned = false;
|
||||
return () => {
|
||||
if (!warned) {
|
||||
warned = true;
|
||||
console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
|
||||
}
|
||||
};
|
||||
})();
|
||||
exports.colors = [
|
||||
"#0000CC",
|
||||
"#0000FF",
|
||||
"#0033CC",
|
||||
"#0033FF",
|
||||
"#0066CC",
|
||||
"#0066FF",
|
||||
"#0099CC",
|
||||
"#0099FF",
|
||||
"#00CC00",
|
||||
"#00CC33",
|
||||
"#00CC66",
|
||||
"#00CC99",
|
||||
"#00CCCC",
|
||||
"#00CCFF",
|
||||
"#3300CC",
|
||||
"#3300FF",
|
||||
"#3333CC",
|
||||
"#3333FF",
|
||||
"#3366CC",
|
||||
"#3366FF",
|
||||
"#3399CC",
|
||||
"#3399FF",
|
||||
"#33CC00",
|
||||
"#33CC33",
|
||||
"#33CC66",
|
||||
"#33CC99",
|
||||
"#33CCCC",
|
||||
"#33CCFF",
|
||||
"#6600CC",
|
||||
"#6600FF",
|
||||
"#6633CC",
|
||||
"#6633FF",
|
||||
"#66CC00",
|
||||
"#66CC33",
|
||||
"#9900CC",
|
||||
"#9900FF",
|
||||
"#9933CC",
|
||||
"#9933FF",
|
||||
"#99CC00",
|
||||
"#99CC33",
|
||||
"#CC0000",
|
||||
"#CC0033",
|
||||
"#CC0066",
|
||||
"#CC0099",
|
||||
"#CC00CC",
|
||||
"#CC00FF",
|
||||
"#CC3300",
|
||||
"#CC3333",
|
||||
"#CC3366",
|
||||
"#CC3399",
|
||||
"#CC33CC",
|
||||
"#CC33FF",
|
||||
"#CC6600",
|
||||
"#CC6633",
|
||||
"#CC9900",
|
||||
"#CC9933",
|
||||
"#CCCC00",
|
||||
"#CCCC33",
|
||||
"#FF0000",
|
||||
"#FF0033",
|
||||
"#FF0066",
|
||||
"#FF0099",
|
||||
"#FF00CC",
|
||||
"#FF00FF",
|
||||
"#FF3300",
|
||||
"#FF3333",
|
||||
"#FF3366",
|
||||
"#FF3399",
|
||||
"#FF33CC",
|
||||
"#FF33FF",
|
||||
"#FF6600",
|
||||
"#FF6633",
|
||||
"#FF9900",
|
||||
"#FF9933",
|
||||
"#FFCC00",
|
||||
"#FFCC33"
|
||||
];
|
||||
function useColors() {
|
||||
if (typeof window !== "undefined" && window.process && (window.process.type === "renderer" || window.process.__nwjs)) {
|
||||
return true;
|
||||
}
|
||||
if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
|
||||
return false;
|
||||
}
|
||||
let m;
|
||||
return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
|
||||
typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
|
||||
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
|
||||
typeof navigator !== "undefined" && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
|
||||
typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
|
||||
}
|
||||
__name(useColors, "useColors");
|
||||
function formatArgs(args) {
|
||||
args[0] = (this.useColors ? "%c" : "") + this.namespace + (this.useColors ? " %c" : " ") + args[0] + (this.useColors ? "%c " : " ") + "+" + module.exports.humanize(this.diff);
|
||||
if (!this.useColors) {
|
||||
return;
|
||||
}
|
||||
const c = "color: " + this.color;
|
||||
args.splice(1, 0, c, "color: inherit");
|
||||
let index = 0;
|
||||
let lastC = 0;
|
||||
args[0].replace(/%[a-zA-Z%]/g, (match) => {
|
||||
if (match === "%%") {
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
if (match === "%c") {
|
||||
lastC = index;
|
||||
}
|
||||
});
|
||||
args.splice(lastC, 0, c);
|
||||
}
|
||||
__name(formatArgs, "formatArgs");
|
||||
exports.log = console.debug || console.log || (() => {
|
||||
});
|
||||
function save(namespaces) {
|
||||
try {
|
||||
if (namespaces) {
|
||||
exports.storage.setItem("debug", namespaces);
|
||||
} else {
|
||||
exports.storage.removeItem("debug");
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
__name(save, "save");
|
||||
function load() {
|
||||
let r;
|
||||
try {
|
||||
r = exports.storage.getItem("debug") || exports.storage.getItem("DEBUG");
|
||||
} catch (error) {
|
||||
}
|
||||
if (!r && typeof process !== "undefined" && "env" in process) {
|
||||
r = process.env.DEBUG;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
__name(load, "load");
|
||||
function localstorage() {
|
||||
try {
|
||||
return localStorage;
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
__name(localstorage, "localstorage");
|
||||
module.exports = require_common()(exports);
|
||||
var { formatters } = module.exports;
|
||||
formatters.j = function(v) {
|
||||
try {
|
||||
return JSON.stringify(v);
|
||||
} catch (error) {
|
||||
return "[UnexpectedJSONParseError]: " + error.message;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// ../../node_modules/.pnpm/supports-color@10.2.2/node_modules/supports-color/index.js
|
||||
var supports_color_exports = {};
|
||||
__export(supports_color_exports, {
|
||||
createSupportsColor: () => createSupportsColor,
|
||||
default: () => supports_color_default
|
||||
});
|
||||
import process2 from "node:process";
|
||||
import os from "node:os";
|
||||
import tty from "node:tty";
|
||||
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
||||
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
||||
const position = argv.indexOf(prefix + flag);
|
||||
const terminatorPosition = argv.indexOf("--");
|
||||
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
||||
}
|
||||
function envForceColor() {
|
||||
if (!("FORCE_COLOR" in env)) {
|
||||
return;
|
||||
}
|
||||
if (env.FORCE_COLOR === "true") {
|
||||
return 1;
|
||||
}
|
||||
if (env.FORCE_COLOR === "false") {
|
||||
return 0;
|
||||
}
|
||||
if (env.FORCE_COLOR.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
||||
if (![0, 1, 2, 3].includes(level)) {
|
||||
return;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
function translateLevel(level) {
|
||||
if (level === 0) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
level,
|
||||
hasBasic: true,
|
||||
has256: level >= 2,
|
||||
has16m: level >= 3
|
||||
};
|
||||
}
|
||||
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
||||
const noFlagForceColor = envForceColor();
|
||||
if (noFlagForceColor !== void 0) {
|
||||
flagForceColor = noFlagForceColor;
|
||||
}
|
||||
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
||||
if (forceColor === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (sniffFlags) {
|
||||
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
||||
return 3;
|
||||
}
|
||||
if (hasFlag("color=256")) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
||||
return 1;
|
||||
}
|
||||
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
||||
return 0;
|
||||
}
|
||||
const min = forceColor || 0;
|
||||
if (env.TERM === "dumb") {
|
||||
return min;
|
||||
}
|
||||
if (process2.platform === "win32") {
|
||||
const osRelease = os.release().split(".");
|
||||
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
||||
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if ("CI" in env) {
|
||||
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
|
||||
return 3;
|
||||
}
|
||||
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
||||
return 1;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
if ("TEAMCITY_VERSION" in env) {
|
||||
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
||||
}
|
||||
if (env.COLORTERM === "truecolor") {
|
||||
return 3;
|
||||
}
|
||||
if (env.TERM === "xterm-kitty") {
|
||||
return 3;
|
||||
}
|
||||
if (env.TERM === "xterm-ghostty") {
|
||||
return 3;
|
||||
}
|
||||
if (env.TERM === "wezterm") {
|
||||
return 3;
|
||||
}
|
||||
if ("TERM_PROGRAM" in env) {
|
||||
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
||||
switch (env.TERM_PROGRAM) {
|
||||
case "iTerm.app": {
|
||||
return version >= 3 ? 3 : 2;
|
||||
}
|
||||
case "Apple_Terminal": {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (/-256(color)?$/i.test(env.TERM)) {
|
||||
return 2;
|
||||
}
|
||||
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
||||
return 1;
|
||||
}
|
||||
if ("COLORTERM" in env) {
|
||||
return 1;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
function createSupportsColor(stream, options = {}) {
|
||||
const level = _supportsColor(stream, {
|
||||
streamIsTTY: stream && stream.isTTY,
|
||||
...options
|
||||
});
|
||||
return translateLevel(level);
|
||||
}
|
||||
var env, flagForceColor, supportsColor, supports_color_default;
|
||||
var init_supports_color = __esm({
|
||||
"../../node_modules/.pnpm/supports-color@10.2.2/node_modules/supports-color/index.js"() {
|
||||
__name(hasFlag, "hasFlag");
|
||||
({ env } = process2);
|
||||
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
||||
flagForceColor = 0;
|
||||
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
||||
flagForceColor = 1;
|
||||
}
|
||||
__name(envForceColor, "envForceColor");
|
||||
__name(translateLevel, "translateLevel");
|
||||
__name(_supportsColor, "_supportsColor");
|
||||
__name(createSupportsColor, "createSupportsColor");
|
||||
supportsColor = {
|
||||
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
||||
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
||||
};
|
||||
supports_color_default = supportsColor;
|
||||
}
|
||||
});
|
||||
|
||||
// ../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/node.js
|
||||
var require_node = __commonJS({
|
||||
"../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/node.js"(exports, module) {
|
||||
var tty2 = __require("tty");
|
||||
var util = __require("util");
|
||||
exports.init = init;
|
||||
exports.log = log;
|
||||
exports.formatArgs = formatArgs;
|
||||
exports.save = save;
|
||||
exports.load = load;
|
||||
exports.useColors = useColors;
|
||||
exports.destroy = util.deprecate(
|
||||
() => {
|
||||
},
|
||||
"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."
|
||||
);
|
||||
exports.colors = [6, 2, 3, 4, 5, 1];
|
||||
try {
|
||||
const supportsColor2 = (init_supports_color(), __toCommonJS(supports_color_exports));
|
||||
if (supportsColor2 && (supportsColor2.stderr || supportsColor2).level >= 2) {
|
||||
exports.colors = [
|
||||
20,
|
||||
21,
|
||||
26,
|
||||
27,
|
||||
32,
|
||||
33,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
56,
|
||||
57,
|
||||
62,
|
||||
63,
|
||||
68,
|
||||
69,
|
||||
74,
|
||||
75,
|
||||
76,
|
||||
77,
|
||||
78,
|
||||
79,
|
||||
80,
|
||||
81,
|
||||
92,
|
||||
93,
|
||||
98,
|
||||
99,
|
||||
112,
|
||||
113,
|
||||
128,
|
||||
129,
|
||||
134,
|
||||
135,
|
||||
148,
|
||||
149,
|
||||
160,
|
||||
161,
|
||||
162,
|
||||
163,
|
||||
164,
|
||||
165,
|
||||
166,
|
||||
167,
|
||||
168,
|
||||
169,
|
||||
170,
|
||||
171,
|
||||
172,
|
||||
173,
|
||||
178,
|
||||
179,
|
||||
184,
|
||||
185,
|
||||
196,
|
||||
197,
|
||||
198,
|
||||
199,
|
||||
200,
|
||||
201,
|
||||
202,
|
||||
203,
|
||||
204,
|
||||
205,
|
||||
206,
|
||||
207,
|
||||
208,
|
||||
209,
|
||||
214,
|
||||
215,
|
||||
220,
|
||||
221
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
exports.inspectOpts = Object.keys(process.env).filter((key) => {
|
||||
return /^debug_/i.test(key);
|
||||
}).reduce((obj, key) => {
|
||||
const prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, (_, k) => {
|
||||
return k.toUpperCase();
|
||||
});
|
||||
let val = process.env[key];
|
||||
if (/^(yes|on|true|enabled)$/i.test(val)) {
|
||||
val = true;
|
||||
} else if (/^(no|off|false|disabled)$/i.test(val)) {
|
||||
val = false;
|
||||
} else if (val === "null") {
|
||||
val = null;
|
||||
} else {
|
||||
val = Number(val);
|
||||
}
|
||||
obj[prop] = val;
|
||||
return obj;
|
||||
}, {});
|
||||
function useColors() {
|
||||
return "colors" in exports.inspectOpts ? Boolean(exports.inspectOpts.colors) : tty2.isatty(process.stderr.fd);
|
||||
}
|
||||
__name(useColors, "useColors");
|
||||
function formatArgs(args) {
|
||||
const { namespace: name, useColors: useColors2 } = this;
|
||||
if (useColors2) {
|
||||
const c = this.color;
|
||||
const colorCode = "\x1B[3" + (c < 8 ? c : "8;5;" + c);
|
||||
const prefix = ` ${colorCode};1m${name} \x1B[0m`;
|
||||
args[0] = prefix + args[0].split("\n").join("\n" + prefix);
|
||||
args.push(colorCode + "m+" + module.exports.humanize(this.diff) + "\x1B[0m");
|
||||
} else {
|
||||
args[0] = getDate() + name + " " + args[0];
|
||||
}
|
||||
}
|
||||
__name(formatArgs, "formatArgs");
|
||||
function getDate() {
|
||||
if (exports.inspectOpts.hideDate) {
|
||||
return "";
|
||||
}
|
||||
return (/* @__PURE__ */ new Date()).toISOString() + " ";
|
||||
}
|
||||
__name(getDate, "getDate");
|
||||
function log(...args) {
|
||||
return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + "\n");
|
||||
}
|
||||
__name(log, "log");
|
||||
function save(namespaces) {
|
||||
if (namespaces) {
|
||||
process.env.DEBUG = namespaces;
|
||||
} else {
|
||||
delete process.env.DEBUG;
|
||||
}
|
||||
}
|
||||
__name(save, "save");
|
||||
function load() {
|
||||
return process.env.DEBUG;
|
||||
}
|
||||
__name(load, "load");
|
||||
function init(debug) {
|
||||
debug.inspectOpts = {};
|
||||
const keys = Object.keys(exports.inspectOpts);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
|
||||
}
|
||||
}
|
||||
__name(init, "init");
|
||||
module.exports = require_common()(exports);
|
||||
var { formatters } = module.exports;
|
||||
formatters.o = function(v) {
|
||||
this.inspectOpts.colors = this.useColors;
|
||||
return util.inspect(v, this.inspectOpts).split("\n").map((str) => str.trim()).join(" ");
|
||||
};
|
||||
formatters.O = function(v) {
|
||||
this.inspectOpts.colors = this.useColors;
|
||||
return util.inspect(v, this.inspectOpts);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// ../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/index.js
|
||||
var require_src = __commonJS({
|
||||
"../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/index.js"(exports, module) {
|
||||
if (typeof process === "undefined" || process.type === "renderer" || process.browser === true || process.__nwjs) {
|
||||
module.exports = require_browser();
|
||||
} else {
|
||||
module.exports = require_node();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export {
|
||||
require_src
|
||||
};
|
||||
+5494
File diff suppressed because it is too large
Load Diff
+60
@@ -0,0 +1,60 @@
|
||||
import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
|
||||
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 __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
||||
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
||||
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
||||
}) : x)(function(x) {
|
||||
if (typeof require !== "undefined") return require.apply(this, arguments);
|
||||
throw Error('Dynamic require of "' + x + '" is not supported');
|
||||
});
|
||||
var __esm = (fn, res, err) => function __init() {
|
||||
if (err) throw err[0];
|
||||
try {
|
||||
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
||||
} catch (e) {
|
||||
throw err = [e], e;
|
||||
}
|
||||
};
|
||||
var __commonJS = (cb, mod) => function __require2() {
|
||||
try {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
} catch (e) {
|
||||
throw mod = 0, e;
|
||||
}
|
||||
};
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
export {
|
||||
__name,
|
||||
__require,
|
||||
__esm,
|
||||
__commonJS,
|
||||
__export,
|
||||
__toESM,
|
||||
__toCommonJS
|
||||
};
|
||||
+64774
File diff suppressed because one or more lines are too long
+53629
File diff suppressed because it is too large
Load Diff
+828
@@ -0,0 +1,828 @@
|
||||
import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
|
||||
import {
|
||||
__commonJS,
|
||||
__name
|
||||
} from "./chunk-T6GW54FI.js";
|
||||
|
||||
// ../../node_modules/.pnpm/bowser@2.14.1/node_modules/bowser/es5.js
|
||||
var require_es5 = __commonJS({
|
||||
"../../node_modules/.pnpm/bowser@2.14.1/node_modules/bowser/es5.js"(exports, module) {
|
||||
!(function(e, t) {
|
||||
"object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.bowser = t() : e.bowser = t();
|
||||
})(exports, (function() {
|
||||
return (function(e) {
|
||||
var t = {};
|
||||
function r(i) {
|
||||
if (t[i]) return t[i].exports;
|
||||
var n = t[i] = { i, l: false, exports: {} };
|
||||
return e[i].call(n.exports, n, n.exports, r), n.l = true, n.exports;
|
||||
}
|
||||
__name(r, "r");
|
||||
return r.m = e, r.c = t, r.d = function(e2, t2, i) {
|
||||
r.o(e2, t2) || Object.defineProperty(e2, t2, { enumerable: true, get: i });
|
||||
}, r.r = function(e2) {
|
||||
"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e2, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e2, "__esModule", { value: true });
|
||||
}, r.t = function(e2, t2) {
|
||||
if (1 & t2 && (e2 = r(e2)), 8 & t2) return e2;
|
||||
if (4 & t2 && "object" == typeof e2 && e2 && e2.__esModule) return e2;
|
||||
var i = /* @__PURE__ */ Object.create(null);
|
||||
if (r.r(i), Object.defineProperty(i, "default", { enumerable: true, value: e2 }), 2 & t2 && "string" != typeof e2) for (var n in e2) r.d(i, n, function(t3) {
|
||||
return e2[t3];
|
||||
}.bind(null, n));
|
||||
return i;
|
||||
}, r.n = function(e2) {
|
||||
var t2 = e2 && e2.__esModule ? function() {
|
||||
return e2.default;
|
||||
} : function() {
|
||||
return e2;
|
||||
};
|
||||
return r.d(t2, "a", t2), t2;
|
||||
}, r.o = function(e2, t2) {
|
||||
return Object.prototype.hasOwnProperty.call(e2, t2);
|
||||
}, r.p = "", r(r.s = 90);
|
||||
})({ 17: function(e, t, r) {
|
||||
"use strict";
|
||||
t.__esModule = true, t.default = void 0;
|
||||
var i = r(18), n = (function() {
|
||||
function e2() {
|
||||
}
|
||||
__name(e2, "e");
|
||||
return e2.getFirstMatch = function(e3, t2) {
|
||||
var r2 = t2.match(e3);
|
||||
return r2 && r2.length > 0 && r2[1] || "";
|
||||
}, e2.getSecondMatch = function(e3, t2) {
|
||||
var r2 = t2.match(e3);
|
||||
return r2 && r2.length > 1 && r2[2] || "";
|
||||
}, e2.matchAndReturnConst = function(e3, t2, r2) {
|
||||
if (e3.test(t2)) return r2;
|
||||
}, e2.getWindowsVersionName = function(e3) {
|
||||
switch (e3) {
|
||||
case "NT":
|
||||
return "NT";
|
||||
case "XP":
|
||||
return "XP";
|
||||
case "NT 5.0":
|
||||
return "2000";
|
||||
case "NT 5.1":
|
||||
return "XP";
|
||||
case "NT 5.2":
|
||||
return "2003";
|
||||
case "NT 6.0":
|
||||
return "Vista";
|
||||
case "NT 6.1":
|
||||
return "7";
|
||||
case "NT 6.2":
|
||||
return "8";
|
||||
case "NT 6.3":
|
||||
return "8.1";
|
||||
case "NT 10.0":
|
||||
return "10";
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}, e2.getMacOSVersionName = function(e3) {
|
||||
var t2 = e3.split(".").splice(0, 2).map((function(e4) {
|
||||
return parseInt(e4, 10) || 0;
|
||||
}));
|
||||
t2.push(0);
|
||||
var r2 = t2[0], i2 = t2[1];
|
||||
if (10 === r2) switch (i2) {
|
||||
case 5:
|
||||
return "Leopard";
|
||||
case 6:
|
||||
return "Snow Leopard";
|
||||
case 7:
|
||||
return "Lion";
|
||||
case 8:
|
||||
return "Mountain Lion";
|
||||
case 9:
|
||||
return "Mavericks";
|
||||
case 10:
|
||||
return "Yosemite";
|
||||
case 11:
|
||||
return "El Capitan";
|
||||
case 12:
|
||||
return "Sierra";
|
||||
case 13:
|
||||
return "High Sierra";
|
||||
case 14:
|
||||
return "Mojave";
|
||||
case 15:
|
||||
return "Catalina";
|
||||
default:
|
||||
return;
|
||||
}
|
||||
switch (r2) {
|
||||
case 11:
|
||||
return "Big Sur";
|
||||
case 12:
|
||||
return "Monterey";
|
||||
case 13:
|
||||
return "Ventura";
|
||||
case 14:
|
||||
return "Sonoma";
|
||||
case 15:
|
||||
return "Sequoia";
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}, e2.getAndroidVersionName = function(e3) {
|
||||
var t2 = e3.split(".").splice(0, 2).map((function(e4) {
|
||||
return parseInt(e4, 10) || 0;
|
||||
}));
|
||||
if (t2.push(0), !(1 === t2[0] && t2[1] < 5)) return 1 === t2[0] && t2[1] < 6 ? "Cupcake" : 1 === t2[0] && t2[1] >= 6 ? "Donut" : 2 === t2[0] && t2[1] < 2 ? "Eclair" : 2 === t2[0] && 2 === t2[1] ? "Froyo" : 2 === t2[0] && t2[1] > 2 ? "Gingerbread" : 3 === t2[0] ? "Honeycomb" : 4 === t2[0] && t2[1] < 1 ? "Ice Cream Sandwich" : 4 === t2[0] && t2[1] < 4 ? "Jelly Bean" : 4 === t2[0] && t2[1] >= 4 ? "KitKat" : 5 === t2[0] ? "Lollipop" : 6 === t2[0] ? "Marshmallow" : 7 === t2[0] ? "Nougat" : 8 === t2[0] ? "Oreo" : 9 === t2[0] ? "Pie" : void 0;
|
||||
}, e2.getVersionPrecision = function(e3) {
|
||||
return e3.split(".").length;
|
||||
}, e2.compareVersions = function(t2, r2, i2) {
|
||||
void 0 === i2 && (i2 = false);
|
||||
var n2 = e2.getVersionPrecision(t2), a = e2.getVersionPrecision(r2), o = Math.max(n2, a), s = 0, u = e2.map([t2, r2], (function(t3) {
|
||||
var r3 = o - e2.getVersionPrecision(t3), i3 = t3 + new Array(r3 + 1).join(".0");
|
||||
return e2.map(i3.split("."), (function(e3) {
|
||||
return new Array(20 - e3.length).join("0") + e3;
|
||||
})).reverse();
|
||||
}));
|
||||
for (i2 && (s = o - Math.min(n2, a)), o -= 1; o >= s; ) {
|
||||
if (u[0][o] > u[1][o]) return 1;
|
||||
if (u[0][o] === u[1][o]) {
|
||||
if (o === s) return 0;
|
||||
o -= 1;
|
||||
} else if (u[0][o] < u[1][o]) return -1;
|
||||
}
|
||||
}, e2.map = function(e3, t2) {
|
||||
var r2, i2 = [];
|
||||
if (Array.prototype.map) return Array.prototype.map.call(e3, t2);
|
||||
for (r2 = 0; r2 < e3.length; r2 += 1) i2.push(t2(e3[r2]));
|
||||
return i2;
|
||||
}, e2.find = function(e3, t2) {
|
||||
var r2, i2;
|
||||
if (Array.prototype.find) return Array.prototype.find.call(e3, t2);
|
||||
for (r2 = 0, i2 = e3.length; r2 < i2; r2 += 1) {
|
||||
var n2 = e3[r2];
|
||||
if (t2(n2, r2)) return n2;
|
||||
}
|
||||
}, e2.assign = function(e3) {
|
||||
for (var t2, r2, i2 = e3, n2 = arguments.length, a = new Array(n2 > 1 ? n2 - 1 : 0), o = 1; o < n2; o++) a[o - 1] = arguments[o];
|
||||
if (Object.assign) return Object.assign.apply(Object, [e3].concat(a));
|
||||
var s = /* @__PURE__ */ __name(function() {
|
||||
var e4 = a[t2];
|
||||
"object" == typeof e4 && null !== e4 && Object.keys(e4).forEach((function(t3) {
|
||||
i2[t3] = e4[t3];
|
||||
}));
|
||||
}, "s");
|
||||
for (t2 = 0, r2 = a.length; t2 < r2; t2 += 1) s();
|
||||
return e3;
|
||||
}, e2.getBrowserAlias = function(e3) {
|
||||
return i.BROWSER_ALIASES_MAP[e3];
|
||||
}, e2.getBrowserTypeByAlias = function(e3) {
|
||||
return i.BROWSER_MAP[e3] || "";
|
||||
}, e2;
|
||||
})();
|
||||
t.default = n, e.exports = t.default;
|
||||
}, 18: function(e, t, r) {
|
||||
"use strict";
|
||||
t.__esModule = true, t.ENGINE_MAP = t.OS_MAP = t.PLATFORMS_MAP = t.BROWSER_MAP = t.BROWSER_ALIASES_MAP = void 0;
|
||||
t.BROWSER_ALIASES_MAP = { AmazonBot: "amazonbot", "Amazon Silk": "amazon_silk", "Android Browser": "android", BaiduSpider: "baiduspider", Bada: "bada", BingCrawler: "bingcrawler", Brave: "brave", BlackBerry: "blackberry", "ChatGPT-User": "chatgpt_user", Chrome: "chrome", ClaudeBot: "claudebot", Chromium: "chromium", Diffbot: "diffbot", DuckDuckBot: "duckduckbot", DuckDuckGo: "duckduckgo", Electron: "electron", Epiphany: "epiphany", FacebookExternalHit: "facebookexternalhit", Firefox: "firefox", Focus: "focus", Generic: "generic", "Google Search": "google_search", Googlebot: "googlebot", GPTBot: "gptbot", "Internet Explorer": "ie", InternetArchiveCrawler: "internetarchivecrawler", "K-Meleon": "k_meleon", LibreWolf: "librewolf", Linespider: "linespider", Maxthon: "maxthon", "Meta-ExternalAds": "meta_externalads", "Meta-ExternalAgent": "meta_externalagent", "Meta-ExternalFetcher": "meta_externalfetcher", "Meta-WebIndexer": "meta_webindexer", "Microsoft Edge": "edge", "MZ Browser": "mz", "NAVER Whale Browser": "naver", "OAI-SearchBot": "oai_searchbot", Omgilibot: "omgilibot", Opera: "opera", "Opera Coast": "opera_coast", "Pale Moon": "pale_moon", PerplexityBot: "perplexitybot", "Perplexity-User": "perplexity_user", PhantomJS: "phantomjs", PingdomBot: "pingdombot", Puffin: "puffin", QQ: "qq", QQLite: "qqlite", QupZilla: "qupzilla", Roku: "roku", Safari: "safari", Sailfish: "sailfish", "Samsung Internet for Android": "samsung_internet", SlackBot: "slackbot", SeaMonkey: "seamonkey", Sleipnir: "sleipnir", "Sogou Browser": "sogou", Swing: "swing", Tizen: "tizen", "UC Browser": "uc", Vivaldi: "vivaldi", "WebOS Browser": "webos", WeChat: "wechat", YahooSlurp: "yahooslurp", "Yandex Browser": "yandex", YandexBot: "yandexbot", YouBot: "youbot" };
|
||||
t.BROWSER_MAP = { amazonbot: "AmazonBot", amazon_silk: "Amazon Silk", android: "Android Browser", baiduspider: "BaiduSpider", bada: "Bada", bingcrawler: "BingCrawler", blackberry: "BlackBerry", brave: "Brave", chatgpt_user: "ChatGPT-User", chrome: "Chrome", claudebot: "ClaudeBot", chromium: "Chromium", diffbot: "Diffbot", duckduckbot: "DuckDuckBot", duckduckgo: "DuckDuckGo", edge: "Microsoft Edge", electron: "Electron", epiphany: "Epiphany", facebookexternalhit: "FacebookExternalHit", firefox: "Firefox", focus: "Focus", generic: "Generic", google_search: "Google Search", googlebot: "Googlebot", gptbot: "GPTBot", ie: "Internet Explorer", internetarchivecrawler: "InternetArchiveCrawler", k_meleon: "K-Meleon", librewolf: "LibreWolf", linespider: "Linespider", maxthon: "Maxthon", meta_externalads: "Meta-ExternalAds", meta_externalagent: "Meta-ExternalAgent", meta_externalfetcher: "Meta-ExternalFetcher", meta_webindexer: "Meta-WebIndexer", mz: "MZ Browser", naver: "NAVER Whale Browser", oai_searchbot: "OAI-SearchBot", omgilibot: "Omgilibot", opera: "Opera", opera_coast: "Opera Coast", pale_moon: "Pale Moon", perplexitybot: "PerplexityBot", perplexity_user: "Perplexity-User", phantomjs: "PhantomJS", pingdombot: "PingdomBot", puffin: "Puffin", qq: "QQ Browser", qqlite: "QQ Browser Lite", qupzilla: "QupZilla", roku: "Roku", safari: "Safari", sailfish: "Sailfish", samsung_internet: "Samsung Internet for Android", seamonkey: "SeaMonkey", slackbot: "SlackBot", sleipnir: "Sleipnir", sogou: "Sogou Browser", swing: "Swing", tizen: "Tizen", uc: "UC Browser", vivaldi: "Vivaldi", webos: "WebOS Browser", wechat: "WeChat", yahooslurp: "YahooSlurp", yandex: "Yandex Browser", yandexbot: "YandexBot", youbot: "YouBot" };
|
||||
t.PLATFORMS_MAP = { bot: "bot", desktop: "desktop", mobile: "mobile", tablet: "tablet", tv: "tv" };
|
||||
t.OS_MAP = { Android: "Android", Bada: "Bada", BlackBerry: "BlackBerry", ChromeOS: "Chrome OS", HarmonyOS: "HarmonyOS", iOS: "iOS", Linux: "Linux", MacOS: "macOS", PlayStation4: "PlayStation 4", Roku: "Roku", Tizen: "Tizen", WebOS: "WebOS", Windows: "Windows", WindowsPhone: "Windows Phone" };
|
||||
t.ENGINE_MAP = { Blink: "Blink", EdgeHTML: "EdgeHTML", Gecko: "Gecko", Presto: "Presto", Trident: "Trident", WebKit: "WebKit" };
|
||||
}, 90: function(e, t, r) {
|
||||
"use strict";
|
||||
t.__esModule = true, t.default = void 0;
|
||||
var i, n = (i = r(91)) && i.__esModule ? i : { default: i }, a = r(18);
|
||||
function o(e2, t2) {
|
||||
for (var r2 = 0; r2 < t2.length; r2++) {
|
||||
var i2 = t2[r2];
|
||||
i2.enumerable = i2.enumerable || false, i2.configurable = true, "value" in i2 && (i2.writable = true), Object.defineProperty(e2, i2.key, i2);
|
||||
}
|
||||
}
|
||||
__name(o, "o");
|
||||
var s = (function() {
|
||||
function e2() {
|
||||
}
|
||||
__name(e2, "e");
|
||||
var t2, r2, i2;
|
||||
return e2.getParser = function(e3, t3, r3) {
|
||||
if (void 0 === t3 && (t3 = false), void 0 === r3 && (r3 = null), "string" != typeof e3) throw new Error("UserAgent should be a string");
|
||||
return new n.default(e3, t3, r3);
|
||||
}, e2.parse = function(e3, t3) {
|
||||
return void 0 === t3 && (t3 = null), new n.default(e3, t3).getResult();
|
||||
}, t2 = e2, i2 = [{ key: "BROWSER_MAP", get: /* @__PURE__ */ __name(function() {
|
||||
return a.BROWSER_MAP;
|
||||
}, "get") }, { key: "ENGINE_MAP", get: /* @__PURE__ */ __name(function() {
|
||||
return a.ENGINE_MAP;
|
||||
}, "get") }, { key: "OS_MAP", get: /* @__PURE__ */ __name(function() {
|
||||
return a.OS_MAP;
|
||||
}, "get") }, { key: "PLATFORMS_MAP", get: /* @__PURE__ */ __name(function() {
|
||||
return a.PLATFORMS_MAP;
|
||||
}, "get") }], (r2 = null) && o(t2.prototype, r2), i2 && o(t2, i2), e2;
|
||||
})();
|
||||
t.default = s, e.exports = t.default;
|
||||
}, 91: function(e, t, r) {
|
||||
"use strict";
|
||||
t.__esModule = true, t.default = void 0;
|
||||
var i = u(r(92)), n = u(r(93)), a = u(r(94)), o = u(r(95)), s = u(r(17));
|
||||
function u(e2) {
|
||||
return e2 && e2.__esModule ? e2 : { default: e2 };
|
||||
}
|
||||
__name(u, "u");
|
||||
var d = (function() {
|
||||
function e2(e3, t3, r2) {
|
||||
if (void 0 === t3 && (t3 = false), void 0 === r2 && (r2 = null), null == e3 || "" === e3) throw new Error("UserAgent parameter can't be empty");
|
||||
this._ua = e3;
|
||||
var i2 = false;
|
||||
"boolean" == typeof t3 ? (i2 = t3, this._hints = r2) : this._hints = null != t3 && "object" == typeof t3 ? t3 : null, this.parsedResult = {}, true !== i2 && this.parse();
|
||||
}
|
||||
__name(e2, "e");
|
||||
var t2 = e2.prototype;
|
||||
return t2.getHints = function() {
|
||||
return this._hints;
|
||||
}, t2.hasBrand = function(e3) {
|
||||
if (!this._hints || !Array.isArray(this._hints.brands)) return false;
|
||||
var t3 = e3.toLowerCase();
|
||||
return this._hints.brands.some((function(e4) {
|
||||
return e4.brand && e4.brand.toLowerCase() === t3;
|
||||
}));
|
||||
}, t2.getBrandVersion = function(e3) {
|
||||
if (this._hints && Array.isArray(this._hints.brands)) {
|
||||
var t3 = e3.toLowerCase(), r2 = this._hints.brands.find((function(e4) {
|
||||
return e4.brand && e4.brand.toLowerCase() === t3;
|
||||
}));
|
||||
return r2 ? r2.version : void 0;
|
||||
}
|
||||
}, t2.getUA = function() {
|
||||
return this._ua;
|
||||
}, t2.test = function(e3) {
|
||||
return e3.test(this._ua);
|
||||
}, t2.parseBrowser = function() {
|
||||
var e3 = this;
|
||||
this.parsedResult.browser = {};
|
||||
var t3 = s.default.find(i.default, (function(t4) {
|
||||
if ("function" == typeof t4.test) return t4.test(e3);
|
||||
if (Array.isArray(t4.test)) return t4.test.some((function(t5) {
|
||||
return e3.test(t5);
|
||||
}));
|
||||
throw new Error("Browser's test function is not valid");
|
||||
}));
|
||||
return t3 && (this.parsedResult.browser = t3.describe(this.getUA(), this)), this.parsedResult.browser;
|
||||
}, t2.getBrowser = function() {
|
||||
return this.parsedResult.browser ? this.parsedResult.browser : this.parseBrowser();
|
||||
}, t2.getBrowserName = function(e3) {
|
||||
return e3 ? String(this.getBrowser().name).toLowerCase() || "" : this.getBrowser().name || "";
|
||||
}, t2.getBrowserVersion = function() {
|
||||
return this.getBrowser().version;
|
||||
}, t2.getOS = function() {
|
||||
return this.parsedResult.os ? this.parsedResult.os : this.parseOS();
|
||||
}, t2.parseOS = function() {
|
||||
var e3 = this;
|
||||
this.parsedResult.os = {};
|
||||
var t3 = s.default.find(n.default, (function(t4) {
|
||||
if ("function" == typeof t4.test) return t4.test(e3);
|
||||
if (Array.isArray(t4.test)) return t4.test.some((function(t5) {
|
||||
return e3.test(t5);
|
||||
}));
|
||||
throw new Error("Browser's test function is not valid");
|
||||
}));
|
||||
return t3 && (this.parsedResult.os = t3.describe(this.getUA())), this.parsedResult.os;
|
||||
}, t2.getOSName = function(e3) {
|
||||
var t3 = this.getOS().name;
|
||||
return e3 ? String(t3).toLowerCase() || "" : t3 || "";
|
||||
}, t2.getOSVersion = function() {
|
||||
return this.getOS().version;
|
||||
}, t2.getPlatform = function() {
|
||||
return this.parsedResult.platform ? this.parsedResult.platform : this.parsePlatform();
|
||||
}, t2.getPlatformType = function(e3) {
|
||||
void 0 === e3 && (e3 = false);
|
||||
var t3 = this.getPlatform().type;
|
||||
return e3 ? String(t3).toLowerCase() || "" : t3 || "";
|
||||
}, t2.parsePlatform = function() {
|
||||
var e3 = this;
|
||||
this.parsedResult.platform = {};
|
||||
var t3 = s.default.find(a.default, (function(t4) {
|
||||
if ("function" == typeof t4.test) return t4.test(e3);
|
||||
if (Array.isArray(t4.test)) return t4.test.some((function(t5) {
|
||||
return e3.test(t5);
|
||||
}));
|
||||
throw new Error("Browser's test function is not valid");
|
||||
}));
|
||||
return t3 && (this.parsedResult.platform = t3.describe(this.getUA())), this.parsedResult.platform;
|
||||
}, t2.getEngine = function() {
|
||||
return this.parsedResult.engine ? this.parsedResult.engine : this.parseEngine();
|
||||
}, t2.getEngineName = function(e3) {
|
||||
return e3 ? String(this.getEngine().name).toLowerCase() || "" : this.getEngine().name || "";
|
||||
}, t2.parseEngine = function() {
|
||||
var e3 = this;
|
||||
this.parsedResult.engine = {};
|
||||
var t3 = s.default.find(o.default, (function(t4) {
|
||||
if ("function" == typeof t4.test) return t4.test(e3);
|
||||
if (Array.isArray(t4.test)) return t4.test.some((function(t5) {
|
||||
return e3.test(t5);
|
||||
}));
|
||||
throw new Error("Browser's test function is not valid");
|
||||
}));
|
||||
return t3 && (this.parsedResult.engine = t3.describe(this.getUA())), this.parsedResult.engine;
|
||||
}, t2.parse = function() {
|
||||
return this.parseBrowser(), this.parseOS(), this.parsePlatform(), this.parseEngine(), this;
|
||||
}, t2.getResult = function() {
|
||||
return s.default.assign({}, this.parsedResult);
|
||||
}, t2.satisfies = function(e3) {
|
||||
var t3 = this, r2 = {}, i2 = 0, n2 = {}, a2 = 0;
|
||||
if (Object.keys(e3).forEach((function(t4) {
|
||||
var o3 = e3[t4];
|
||||
"string" == typeof o3 ? (n2[t4] = o3, a2 += 1) : "object" == typeof o3 && (r2[t4] = o3, i2 += 1);
|
||||
})), i2 > 0) {
|
||||
var o2 = Object.keys(r2), u2 = s.default.find(o2, (function(e4) {
|
||||
return t3.isOS(e4);
|
||||
}));
|
||||
if (u2) {
|
||||
var d2 = this.satisfies(r2[u2]);
|
||||
if (void 0 !== d2) return d2;
|
||||
}
|
||||
var c = s.default.find(o2, (function(e4) {
|
||||
return t3.isPlatform(e4);
|
||||
}));
|
||||
if (c) {
|
||||
var f = this.satisfies(r2[c]);
|
||||
if (void 0 !== f) return f;
|
||||
}
|
||||
}
|
||||
if (a2 > 0) {
|
||||
var l = Object.keys(n2), b = s.default.find(l, (function(e4) {
|
||||
return t3.isBrowser(e4, true);
|
||||
}));
|
||||
if (void 0 !== b) return this.compareVersion(n2[b]);
|
||||
}
|
||||
}, t2.isBrowser = function(e3, t3) {
|
||||
void 0 === t3 && (t3 = false);
|
||||
var r2 = this.getBrowserName().toLowerCase(), i2 = e3.toLowerCase(), n2 = s.default.getBrowserTypeByAlias(i2);
|
||||
return t3 && n2 && (i2 = n2.toLowerCase()), i2 === r2;
|
||||
}, t2.compareVersion = function(e3) {
|
||||
var t3 = [0], r2 = e3, i2 = false, n2 = this.getBrowserVersion();
|
||||
if ("string" == typeof n2) return ">" === e3[0] || "<" === e3[0] ? (r2 = e3.substr(1), "=" === e3[1] ? (i2 = true, r2 = e3.substr(2)) : t3 = [], ">" === e3[0] ? t3.push(1) : t3.push(-1)) : "=" === e3[0] ? r2 = e3.substr(1) : "~" === e3[0] && (i2 = true, r2 = e3.substr(1)), t3.indexOf(s.default.compareVersions(n2, r2, i2)) > -1;
|
||||
}, t2.isOS = function(e3) {
|
||||
return this.getOSName(true) === String(e3).toLowerCase();
|
||||
}, t2.isPlatform = function(e3) {
|
||||
return this.getPlatformType(true) === String(e3).toLowerCase();
|
||||
}, t2.isEngine = function(e3) {
|
||||
return this.getEngineName(true) === String(e3).toLowerCase();
|
||||
}, t2.is = function(e3, t3) {
|
||||
return void 0 === t3 && (t3 = false), this.isBrowser(e3, t3) || this.isOS(e3) || this.isPlatform(e3);
|
||||
}, t2.some = function(e3) {
|
||||
var t3 = this;
|
||||
return void 0 === e3 && (e3 = []), e3.some((function(e4) {
|
||||
return t3.is(e4);
|
||||
}));
|
||||
}, e2;
|
||||
})();
|
||||
t.default = d, e.exports = t.default;
|
||||
}, 92: function(e, t, r) {
|
||||
"use strict";
|
||||
t.__esModule = true, t.default = void 0;
|
||||
var i, n = (i = r(17)) && i.__esModule ? i : { default: i };
|
||||
var a = /version\/(\d+(\.?_?\d+)+)/i, o = [{ test: [/gptbot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "GPTBot" }, r2 = n.default.getFirstMatch(/gptbot\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/chatgpt-user/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "ChatGPT-User" }, r2 = n.default.getFirstMatch(/chatgpt-user\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/oai-searchbot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "OAI-SearchBot" }, r2 = n.default.getFirstMatch(/oai-searchbot\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/claudebot/i, /claude-web/i, /claude-user/i, /claude-searchbot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "ClaudeBot" }, r2 = n.default.getFirstMatch(/(?:claudebot|claude-web|claude-user|claude-searchbot)\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/omgilibot/i, /webzio-extended/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Omgilibot" }, r2 = n.default.getFirstMatch(/(?:omgilibot|webzio-extended)\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/diffbot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Diffbot" }, r2 = n.default.getFirstMatch(/diffbot\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/perplexitybot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "PerplexityBot" }, r2 = n.default.getFirstMatch(/perplexitybot\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/perplexity-user/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Perplexity-User" }, r2 = n.default.getFirstMatch(/perplexity-user\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/youbot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "YouBot" }, r2 = n.default.getFirstMatch(/youbot\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/meta-webindexer/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Meta-WebIndexer" }, r2 = n.default.getFirstMatch(/meta-webindexer\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/meta-externalads/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Meta-ExternalAds" }, r2 = n.default.getFirstMatch(/meta-externalads\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/meta-externalagent/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Meta-ExternalAgent" }, r2 = n.default.getFirstMatch(/meta-externalagent\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/meta-externalfetcher/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Meta-ExternalFetcher" }, r2 = n.default.getFirstMatch(/meta-externalfetcher\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/googlebot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Googlebot" }, r2 = n.default.getFirstMatch(/googlebot\/(\d+(\.\d+))/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/linespider/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Linespider" }, r2 = n.default.getFirstMatch(/(?:linespider)(?:-[-\w]+)?[\s/](\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/amazonbot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "AmazonBot" }, r2 = n.default.getFirstMatch(/amazonbot\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/bingbot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "BingCrawler" }, r2 = n.default.getFirstMatch(/bingbot\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/baiduspider/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "BaiduSpider" }, r2 = n.default.getFirstMatch(/baiduspider\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/duckduckbot/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "DuckDuckBot" }, r2 = n.default.getFirstMatch(/duckduckbot\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/ia_archiver/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "InternetArchiveCrawler" }, r2 = n.default.getFirstMatch(/ia_archiver\/(\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/facebookexternalhit/i, /facebookcatalog/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { name: "FacebookExternalHit" };
|
||||
}, "describe") }, { test: [/slackbot/i, /slack-imgProxy/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "SlackBot" }, r2 = n.default.getFirstMatch(/(?:slackbot|slack-imgproxy)(?:-[-\w]+)?[\s/](\d+(\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/yahoo!?[\s/]*slurp/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { name: "YahooSlurp" };
|
||||
}, "describe") }, { test: [/yandexbot/i, /yandexmobilebot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { name: "YandexBot" };
|
||||
}, "describe") }, { test: [/pingdom/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { name: "PingdomBot" };
|
||||
}, "describe") }, { test: [/opera/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Opera" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/opr\/|opios/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Opera" }, r2 = n.default.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/SamsungBrowser/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Samsung Internet for Android" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/Whale/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "NAVER Whale Browser" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/PaleMoon/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Pale Moon" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:PaleMoon)[\s/](\d+(?:\.\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/MZBrowser/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "MZ Browser" }, r2 = n.default.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/focus/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Focus" }, r2 = n.default.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/swing/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Swing" }, r2 = n.default.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/coast/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Opera Coast" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/opt\/\d+(?:.?_?\d+)+/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Opera Touch" }, r2 = n.default.getFirstMatch(/(?:opt)[\s/](\d+(\.?_?\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/yabrowser/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Yandex Browser" }, r2 = n.default.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/ucbrowser/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "UC Browser" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/Maxthon|mxios/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Maxthon" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/epiphany/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Epiphany" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/puffin/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Puffin" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/sleipnir/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Sleipnir" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/k-meleon/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "K-Meleon" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/micromessenger/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "WeChat" }, r2 = n.default.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/qqbrowser/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: /qqbrowserlite/i.test(e2) ? "QQ Browser Lite" : "QQ Browser" }, r2 = n.default.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/msie|trident/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Internet Explorer" }, r2 = n.default.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/\sedg\//i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Microsoft Edge" }, r2 = n.default.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/edg([ea]|ios)/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Microsoft Edge" }, r2 = n.default.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/vivaldi/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Vivaldi" }, r2 = n.default.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/seamonkey/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "SeaMonkey" }, r2 = n.default.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/sailfish/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Sailfish" }, r2 = n.default.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/silk/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Amazon Silk" }, r2 = n.default.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/phantom/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "PhantomJS" }, r2 = n.default.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/slimerjs/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "SlimerJS" }, r2 = n.default.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/blackberry|\bbb\d+/i, /rim\stablet/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "BlackBerry" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/(web|hpw)[o0]s/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "WebOS Browser" }, r2 = n.default.getFirstMatch(a, e2) || n.default.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/bada/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Bada" }, r2 = n.default.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/tizen/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Tizen" }, r2 = n.default.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/qupzilla/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "QupZilla" }, r2 = n.default.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/librewolf/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "LibreWolf" }, r2 = n.default.getFirstMatch(/(?:librewolf)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/firefox|iceweasel|fxios/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Firefox" }, r2 = n.default.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/electron/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Electron" }, r2 = n.default.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/sogoumobilebrowser/i, /metasr/i, /se 2\.[x]/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Sogou Browser" }, r2 = n.default.getFirstMatch(/(?:sogoumobilebrowser)[\s/](\d+(\.?_?\d+)+)/i, e2), i2 = n.default.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i, e2), a2 = n.default.getFirstMatch(/se ([\d.]+)x/i, e2), o2 = r2 || i2 || a2;
|
||||
return o2 && (t2.version = o2), t2;
|
||||
}, "describe") }, { test: [/MiuiBrowser/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Miui" }, r2 = n.default.getFirstMatch(/(?:MiuiBrowser)[\s/](\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return !!e2.hasBrand("DuckDuckGo") || e2.test(/\sDdg\/[\d.]+$/i);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function(e2, t2) {
|
||||
var r2 = { name: "DuckDuckGo" };
|
||||
if (t2) {
|
||||
var i2 = t2.getBrandVersion("DuckDuckGo");
|
||||
if (i2) return r2.version = i2, r2;
|
||||
}
|
||||
var a2 = n.default.getFirstMatch(/\sDdg\/([\d.]+)$/i, e2);
|
||||
return a2 && (r2.version = a2), r2;
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return e2.hasBrand("Brave");
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function(e2, t2) {
|
||||
var r2 = { name: "Brave" };
|
||||
if (t2) {
|
||||
var i2 = t2.getBrandVersion("Brave");
|
||||
if (i2) return r2.version = i2, r2;
|
||||
}
|
||||
return r2;
|
||||
}, "describe") }, { test: [/chromium/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Chromium" }, r2 = n.default.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i, e2) || n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/chrome|crios|crmo/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Chrome" }, r2 = n.default.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/GSA/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Google Search" }, r2 = n.default.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = !e2.test(/like android/i), r2 = e2.test(/android/i);
|
||||
return t2 && r2;
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Android Browser" }, r2 = n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/playstation 4/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "PlayStation 4" }, r2 = n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/safari|applewebkit/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: "Safari" }, r2 = n.default.getFirstMatch(a, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/.*/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = -1 !== e2.search("\\(") ? /^(.*)\/(.*)[ \t]\((.*)/ : /^(.*)\/(.*) /;
|
||||
return { name: n.default.getFirstMatch(t2, e2), version: n.default.getSecondMatch(t2, e2) };
|
||||
}, "describe") }];
|
||||
t.default = o, e.exports = t.default;
|
||||
}, 93: function(e, t, r) {
|
||||
"use strict";
|
||||
t.__esModule = true, t.default = void 0;
|
||||
var i, n = (i = r(17)) && i.__esModule ? i : { default: i }, a = r(18);
|
||||
var o = [{ test: [/Roku\/DVP/], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/Roku\/DVP-(\d+\.\d+)/i, e2);
|
||||
return { name: a.OS_MAP.Roku, version: t2 };
|
||||
}, "describe") }, { test: [/windows phone/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i, e2);
|
||||
return { name: a.OS_MAP.WindowsPhone, version: t2 };
|
||||
}, "describe") }, { test: [/windows /i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i, e2), r2 = n.default.getWindowsVersionName(t2);
|
||||
return { name: a.OS_MAP.Windows, version: t2, versionName: r2 };
|
||||
}, "describe") }, { test: [/Macintosh(.*?) FxiOS(.*?)\//], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: a.OS_MAP.iOS }, r2 = n.default.getSecondMatch(/(Version\/)(\d[\d.]+)/, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/macintosh/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/mac os x (\d+(\.?_?\d+)+)/i, e2).replace(/[_\s]/g, "."), r2 = n.default.getMacOSVersionName(t2), i2 = { name: a.OS_MAP.MacOS, version: t2 };
|
||||
return r2 && (i2.versionName = r2), i2;
|
||||
}, "describe") }, { test: [/(ipod|iphone|ipad)/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i, e2).replace(/[_\s]/g, ".");
|
||||
return { name: a.OS_MAP.iOS, version: t2 };
|
||||
}, "describe") }, { test: [/OpenHarmony/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/OpenHarmony\s+(\d+(\.\d+)*)/i, e2);
|
||||
return { name: a.OS_MAP.HarmonyOS, version: t2 };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = !e2.test(/like android/i), r2 = e2.test(/android/i);
|
||||
return t2 && r2;
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/android[\s/-](\d+(\.\d+)*)/i, e2), r2 = n.default.getAndroidVersionName(t2), i2 = { name: a.OS_MAP.Android, version: t2 };
|
||||
return r2 && (i2.versionName = r2), i2;
|
||||
}, "describe") }, { test: [/(web|hpw)[o0]s/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/(?:web|hpw)[o0]s\/(\d+(\.\d+)*)/i, e2), r2 = { name: a.OS_MAP.WebOS };
|
||||
return t2 && t2.length && (r2.version = t2), r2;
|
||||
}, "describe") }, { test: [/blackberry|\bbb\d+/i, /rim\stablet/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i, e2) || n.default.getFirstMatch(/blackberry\d+\/(\d+([_\s]\d+)*)/i, e2) || n.default.getFirstMatch(/\bbb(\d+)/i, e2);
|
||||
return { name: a.OS_MAP.BlackBerry, version: t2 };
|
||||
}, "describe") }, { test: [/bada/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/bada\/(\d+(\.\d+)*)/i, e2);
|
||||
return { name: a.OS_MAP.Bada, version: t2 };
|
||||
}, "describe") }, { test: [/tizen/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/tizen[/\s](\d+(\.\d+)*)/i, e2);
|
||||
return { name: a.OS_MAP.Tizen, version: t2 };
|
||||
}, "describe") }, { test: [/linux/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { name: a.OS_MAP.Linux };
|
||||
}, "describe") }, { test: [/CrOS/], describe: /* @__PURE__ */ __name(function() {
|
||||
return { name: a.OS_MAP.ChromeOS };
|
||||
}, "describe") }, { test: [/PlayStation 4/], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/PlayStation 4[/\s](\d+(\.\d+)*)/i, e2);
|
||||
return { name: a.OS_MAP.PlayStation4, version: t2 };
|
||||
}, "describe") }];
|
||||
t.default = o, e.exports = t.default;
|
||||
}, 94: function(e, t, r) {
|
||||
"use strict";
|
||||
t.__esModule = true, t.default = void 0;
|
||||
var i, n = (i = r(17)) && i.__esModule ? i : { default: i }, a = r(18);
|
||||
var o = [{ test: [/googlebot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Google" };
|
||||
}, "describe") }, { test: [/linespider/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Line" };
|
||||
}, "describe") }, { test: [/amazonbot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Amazon" };
|
||||
}, "describe") }, { test: [/gptbot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "OpenAI" };
|
||||
}, "describe") }, { test: [/chatgpt-user/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "OpenAI" };
|
||||
}, "describe") }, { test: [/oai-searchbot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "OpenAI" };
|
||||
}, "describe") }, { test: [/baiduspider/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Baidu" };
|
||||
}, "describe") }, { test: [/bingbot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Bing" };
|
||||
}, "describe") }, { test: [/duckduckbot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "DuckDuckGo" };
|
||||
}, "describe") }, { test: [/claudebot/i, /claude-web/i, /claude-user/i, /claude-searchbot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Anthropic" };
|
||||
}, "describe") }, { test: [/omgilibot/i, /webzio-extended/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Webz.io" };
|
||||
}, "describe") }, { test: [/diffbot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Diffbot" };
|
||||
}, "describe") }, { test: [/perplexitybot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Perplexity AI" };
|
||||
}, "describe") }, { test: [/perplexity-user/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Perplexity AI" };
|
||||
}, "describe") }, { test: [/youbot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "You.com" };
|
||||
}, "describe") }, { test: [/ia_archiver/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Internet Archive" };
|
||||
}, "describe") }, { test: [/meta-webindexer/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Meta" };
|
||||
}, "describe") }, { test: [/meta-externalads/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Meta" };
|
||||
}, "describe") }, { test: [/meta-externalagent/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Meta" };
|
||||
}, "describe") }, { test: [/meta-externalfetcher/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Meta" };
|
||||
}, "describe") }, { test: [/facebookexternalhit/i, /facebookcatalog/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Meta" };
|
||||
}, "describe") }, { test: [/slackbot/i, /slack-imgProxy/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Slack" };
|
||||
}, "describe") }, { test: [/yahoo/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Yahoo" };
|
||||
}, "describe") }, { test: [/yandexbot/i, /yandexmobilebot/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Yandex" };
|
||||
}, "describe") }, { test: [/pingdom/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.bot, vendor: "Pingdom" };
|
||||
}, "describe") }, { test: [/huawei/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/(can-l01)/i, e2) && "Nova", r2 = { type: a.PLATFORMS_MAP.mobile, vendor: "Huawei" };
|
||||
return t2 && (r2.model = t2), r2;
|
||||
}, "describe") }, { test: [/nexus\s*(?:7|8|9|10).*/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tablet, vendor: "Nexus" };
|
||||
}, "describe") }, { test: [/ipad/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tablet, vendor: "Apple", model: "iPad" };
|
||||
}, "describe") }, { test: [/Macintosh(.*?) FxiOS(.*?)\//], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tablet, vendor: "Apple", model: "iPad" };
|
||||
}, "describe") }, { test: [/kftt build/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tablet, vendor: "Amazon", model: "Kindle Fire HD 7" };
|
||||
}, "describe") }, { test: [/silk/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tablet, vendor: "Amazon" };
|
||||
}, "describe") }, { test: [/tablet(?! pc)/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tablet };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = e2.test(/ipod|iphone/i), r2 = e2.test(/like (ipod|iphone)/i);
|
||||
return t2 && !r2;
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/(ipod|iphone)/i, e2);
|
||||
return { type: a.PLATFORMS_MAP.mobile, vendor: "Apple", model: t2 };
|
||||
}, "describe") }, { test: [/nexus\s*[0-6].*/i, /galaxy nexus/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.mobile, vendor: "Nexus" };
|
||||
}, "describe") }, { test: [/Nokia/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = n.default.getFirstMatch(/Nokia\s+([0-9]+(\.[0-9]+)?)/i, e2), r2 = { type: a.PLATFORMS_MAP.mobile, vendor: "Nokia" };
|
||||
return t2 && (r2.model = t2), r2;
|
||||
}, "describe") }, { test: [/[^-]mobi/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.mobile };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "blackberry" === e2.getBrowserName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.mobile, vendor: "BlackBerry" };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "bada" === e2.getBrowserName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.mobile };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "windows phone" === e2.getBrowserName();
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.mobile, vendor: "Microsoft" };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = Number(String(e2.getOSVersion()).split(".")[0]);
|
||||
return "android" === e2.getOSName(true) && t2 >= 3;
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tablet };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "android" === e2.getOSName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.mobile };
|
||||
}, "describe") }, { test: [/smart-?tv|smarttv/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tv };
|
||||
}, "describe") }, { test: [/netcast/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tv };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "macos" === e2.getOSName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.desktop, vendor: "Apple" };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "windows" === e2.getOSName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.desktop };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "linux" === e2.getOSName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.desktop };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "playstation 4" === e2.getOSName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tv };
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "roku" === e2.getOSName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function() {
|
||||
return { type: a.PLATFORMS_MAP.tv };
|
||||
}, "describe") }];
|
||||
t.default = o, e.exports = t.default;
|
||||
}, 95: function(e, t, r) {
|
||||
"use strict";
|
||||
t.__esModule = true, t.default = void 0;
|
||||
var i, n = (i = r(17)) && i.__esModule ? i : { default: i }, a = r(18);
|
||||
var o = [{ test: /* @__PURE__ */ __name(function(e2) {
|
||||
return "microsoft edge" === e2.getBrowserName(true);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function(e2) {
|
||||
if (/\sedg\//i.test(e2)) return { name: a.ENGINE_MAP.Blink };
|
||||
var t2 = n.default.getFirstMatch(/edge\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return { name: a.ENGINE_MAP.EdgeHTML, version: t2 };
|
||||
}, "describe") }, { test: [/trident/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: a.ENGINE_MAP.Trident }, r2 = n.default.getFirstMatch(/trident\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
return e2.test(/presto/i);
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: a.ENGINE_MAP.Presto }, r2 = n.default.getFirstMatch(/presto\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = e2.test(/gecko/i), r2 = e2.test(/like gecko/i);
|
||||
return t2 && !r2;
|
||||
}, "test"), describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: a.ENGINE_MAP.Gecko }, r2 = n.default.getFirstMatch(/gecko\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }, { test: [/(apple)?webkit\/537\.36/i], describe: /* @__PURE__ */ __name(function() {
|
||||
return { name: a.ENGINE_MAP.Blink };
|
||||
}, "describe") }, { test: [/(apple)?webkit/i], describe: /* @__PURE__ */ __name(function(e2) {
|
||||
var t2 = { name: a.ENGINE_MAP.WebKit }, r2 = n.default.getFirstMatch(/webkit\/(\d+(\.?_?\d+)+)/i, e2);
|
||||
return r2 && (t2.version = r2), t2;
|
||||
}, "describe") }];
|
||||
t.default = o, e.exports = t.default;
|
||||
} });
|
||||
}));
|
||||
}
|
||||
});
|
||||
export default require_es5();
|
||||
@@ -0,0 +1,44 @@
|
||||
import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
|
||||
import {
|
||||
EventStreamCodec,
|
||||
EventStreamMarshaller,
|
||||
EventStreamMarshaller2,
|
||||
EventStreamSerde,
|
||||
HeaderMarshaller,
|
||||
Int64,
|
||||
MessageDecoderStream,
|
||||
MessageEncoderStream,
|
||||
SmithyMessageDecoderStream,
|
||||
SmithyMessageEncoderStream,
|
||||
eventStreamSerdeProvider,
|
||||
eventStreamSerdeProvider2,
|
||||
getChunkedStream,
|
||||
getMessageUnmarshaller,
|
||||
getUnmarshalledStream,
|
||||
init_event_streams,
|
||||
iterableToReadableStream,
|
||||
readableStreamToIterable,
|
||||
resolveEventStreamSerdeConfig
|
||||
} from "./chunk-6UPY3HEI.js";
|
||||
import "./chunk-T6GW54FI.js";
|
||||
init_event_streams();
|
||||
export {
|
||||
EventStreamCodec,
|
||||
EventStreamMarshaller2 as EventStreamMarshaller,
|
||||
EventStreamSerde,
|
||||
HeaderMarshaller,
|
||||
Int64,
|
||||
MessageDecoderStream,
|
||||
MessageEncoderStream,
|
||||
SmithyMessageDecoderStream,
|
||||
SmithyMessageEncoderStream,
|
||||
EventStreamMarshaller as UniversalEventStreamMarshaller,
|
||||
eventStreamSerdeProvider2 as eventStreamSerdeProvider,
|
||||
getChunkedStream,
|
||||
getMessageUnmarshaller,
|
||||
getUnmarshalledStream,
|
||||
iterableToReadableStream,
|
||||
readableStreamToIterable,
|
||||
resolveEventStreamSerdeConfig,
|
||||
eventStreamSerdeProvider as universalEventStreamSerdeProvider
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
|
||||
import {
|
||||
AnyFilesInterceptor,
|
||||
ExpressAdapter,
|
||||
FileFieldsInterceptor,
|
||||
FileInterceptor,
|
||||
FilesInterceptor,
|
||||
MulterModule,
|
||||
NoFilesInterceptor,
|
||||
init_platform_express
|
||||
} from "./chunk-XSOBN2CI.js";
|
||||
import "./chunk-BX2ML7LW.js";
|
||||
import "./chunk-C4DYPKAN.js";
|
||||
import "./chunk-A34KYKJP.js";
|
||||
import "./chunk-T6GW54FI.js";
|
||||
init_platform_express();
|
||||
export {
|
||||
AnyFilesInterceptor,
|
||||
ExpressAdapter,
|
||||
FileFieldsInterceptor,
|
||||
FileInterceptor,
|
||||
FilesInterceptor,
|
||||
MulterModule,
|
||||
NoFilesInterceptor
|
||||
};
|
||||
@@ -0,0 +1,349 @@
|
||||
import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
|
||||
import {
|
||||
__name
|
||||
} from "./chunk-T6GW54FI.js";
|
||||
|
||||
// ../../node_modules/.pnpm/@prisma+client@7.10.1_prism_1fac53bcaa5c7503395b340f2e6f2cd9/node_modules/@prisma/client/runtime/query_compiler_fast_bg.postgresql.mjs
|
||||
var h = /* @__PURE__ */ __name(() => {
|
||||
}, "h");
|
||||
h.prototype = h;
|
||||
var o;
|
||||
function D(e) {
|
||||
o = e;
|
||||
}
|
||||
__name(D, "D");
|
||||
var p = null;
|
||||
function a() {
|
||||
return (p === null || p.byteLength === 0) && (p = new Uint8Array(o.memory.buffer)), p;
|
||||
}
|
||||
__name(a, "a");
|
||||
var y = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true });
|
||||
y.decode();
|
||||
var E = 2146435072;
|
||||
var A = 0;
|
||||
function T(e, t) {
|
||||
return A += t, A >= E && (y = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }), y.decode(), A = t), y.decode(a().subarray(e, e + t));
|
||||
}
|
||||
__name(T, "T");
|
||||
function m(e, t) {
|
||||
return e = e >>> 0, T(e, t);
|
||||
}
|
||||
__name(m, "m");
|
||||
var f = 0;
|
||||
var g = new TextEncoder();
|
||||
"encodeInto" in g || (g.encodeInto = function(e, t) {
|
||||
const n = g.encode(e);
|
||||
return t.set(n), { read: e.length, written: n.length };
|
||||
});
|
||||
function l(e, t, n) {
|
||||
if (n === void 0) {
|
||||
const i = g.encode(e), d = t(i.length, 1) >>> 0;
|
||||
return a().subarray(d, d + i.length).set(i), f = i.length, d;
|
||||
}
|
||||
let _ = e.length, r = t(_, 1) >>> 0;
|
||||
const s = a();
|
||||
let c = 0;
|
||||
for (; c < _; c++) {
|
||||
const i = e.charCodeAt(c);
|
||||
if (i > 127) break;
|
||||
s[r + c] = i;
|
||||
}
|
||||
if (c !== _) {
|
||||
c !== 0 && (e = e.slice(c)), r = n(r, _, _ = c + e.length * 3, 1) >>> 0;
|
||||
const i = a().subarray(r + c, r + _), d = g.encodeInto(e, i);
|
||||
c += d.written, r = n(r, _, c, 1) >>> 0;
|
||||
}
|
||||
return f = c, r;
|
||||
}
|
||||
__name(l, "l");
|
||||
var b = null;
|
||||
function u() {
|
||||
return (b === null || b.buffer.detached === true || b.buffer.detached === void 0 && b.buffer !== o.memory.buffer) && (b = new DataView(o.memory.buffer)), b;
|
||||
}
|
||||
__name(u, "u");
|
||||
function x(e) {
|
||||
return e == null;
|
||||
}
|
||||
__name(x, "x");
|
||||
function S(e) {
|
||||
const t = typeof e;
|
||||
if (t == "number" || t == "boolean" || e == null) return `${e}`;
|
||||
if (t == "string") return `"${e}"`;
|
||||
if (t == "symbol") {
|
||||
const r = e.description;
|
||||
return r == null ? "Symbol" : `Symbol(${r})`;
|
||||
}
|
||||
if (t == "function") {
|
||||
const r = e.name;
|
||||
return typeof r == "string" && r.length > 0 ? `Function(${r})` : "Function";
|
||||
}
|
||||
if (Array.isArray(e)) {
|
||||
const r = e.length;
|
||||
let s = "[";
|
||||
r > 0 && (s += S(e[0]));
|
||||
for (let c = 1; c < r; c++) s += ", " + S(e[c]);
|
||||
return s += "]", s;
|
||||
}
|
||||
const n = /\[object ([^\]]+)\]/.exec(toString.call(e));
|
||||
let _;
|
||||
if (n && n.length > 1) _ = n[1];
|
||||
else return toString.call(e);
|
||||
if (_ == "Object") try {
|
||||
return "Object(" + JSON.stringify(e) + ")";
|
||||
} catch {
|
||||
return "Object";
|
||||
}
|
||||
return e instanceof Error ? `${e.name}: ${e.message}
|
||||
${e.stack}` : _;
|
||||
}
|
||||
__name(S, "S");
|
||||
function M(e, t) {
|
||||
return e = e >>> 0, a().subarray(e / 1, e / 1 + t);
|
||||
}
|
||||
__name(M, "M");
|
||||
function w(e) {
|
||||
const t = o.__wbindgen_externrefs.get(e);
|
||||
return o.__externref_table_dealloc(e), t;
|
||||
}
|
||||
__name(w, "w");
|
||||
var I = typeof FinalizationRegistry > "u" ? { register: /* @__PURE__ */ __name(() => {
|
||||
}, "register"), unregister: /* @__PURE__ */ __name(() => {
|
||||
}, "unregister") } : new FinalizationRegistry((e) => o.__wbg_querycompiler_free(e >>> 0, 1));
|
||||
var F = class {
|
||||
static {
|
||||
__name(this, "F");
|
||||
}
|
||||
__destroy_into_raw() {
|
||||
const t = this.__wbg_ptr;
|
||||
return this.__wbg_ptr = 0, I.unregister(this), t;
|
||||
}
|
||||
free() {
|
||||
const t = this.__destroy_into_raw();
|
||||
o.__wbg_querycompiler_free(t, 0);
|
||||
}
|
||||
compileBatch(t) {
|
||||
const n = l(t, o.__wbindgen_malloc, o.__wbindgen_realloc), _ = f, r = o.querycompiler_compileBatch(this.__wbg_ptr, n, _);
|
||||
if (r[2]) throw w(r[1]);
|
||||
return w(r[0]);
|
||||
}
|
||||
constructor(t) {
|
||||
const n = o.querycompiler_new(t);
|
||||
if (n[2]) throw w(n[1]);
|
||||
return this.__wbg_ptr = n[0] >>> 0, I.register(this, this.__wbg_ptr, this), this;
|
||||
}
|
||||
compile(t) {
|
||||
const n = l(t, o.__wbindgen_malloc, o.__wbindgen_realloc), _ = f, r = o.querycompiler_compile(this.__wbg_ptr, n, _);
|
||||
if (r[2]) throw w(r[1]);
|
||||
return w(r[0]);
|
||||
}
|
||||
};
|
||||
Symbol.dispose && (F.prototype[Symbol.dispose] = F.prototype.free);
|
||||
function O(e, t) {
|
||||
return Error(m(e, t));
|
||||
}
|
||||
__name(O, "O");
|
||||
function B(e) {
|
||||
return Number(e);
|
||||
}
|
||||
__name(B, "B");
|
||||
function N(e, t) {
|
||||
const n = String(t), _ = l(n, o.__wbindgen_malloc, o.__wbindgen_realloc), r = f;
|
||||
u().setInt32(e + 4 * 1, r, true), u().setInt32(e + 4 * 0, _, true);
|
||||
}
|
||||
__name(N, "N");
|
||||
function U(e) {
|
||||
const t = e, n = typeof t == "boolean" ? t : void 0;
|
||||
return x(n) ? 16777215 : n ? 1 : 0;
|
||||
}
|
||||
__name(U, "U");
|
||||
function R(e, t) {
|
||||
const n = S(t), _ = l(n, o.__wbindgen_malloc, o.__wbindgen_realloc), r = f;
|
||||
u().setInt32(e + 4 * 1, r, true), u().setInt32(e + 4 * 0, _, true);
|
||||
}
|
||||
__name(R, "R");
|
||||
function $(e, t) {
|
||||
return e in t;
|
||||
}
|
||||
__name($, "$");
|
||||
function q(e) {
|
||||
const t = e;
|
||||
return typeof t == "object" && t !== null;
|
||||
}
|
||||
__name(q, "q");
|
||||
function C(e) {
|
||||
return typeof e == "string";
|
||||
}
|
||||
__name(C, "C");
|
||||
function k(e) {
|
||||
return e === void 0;
|
||||
}
|
||||
__name(k, "k");
|
||||
function W(e, t) {
|
||||
return e == t;
|
||||
}
|
||||
__name(W, "W");
|
||||
function V(e, t) {
|
||||
const n = t, _ = typeof n == "number" ? n : void 0;
|
||||
u().setFloat64(e + 8 * 1, x(_) ? 0 : _, true), u().setInt32(e + 4 * 0, !x(_), true);
|
||||
}
|
||||
__name(V, "V");
|
||||
function z(e, t) {
|
||||
const n = t, _ = typeof n == "string" ? n : void 0;
|
||||
var r = x(_) ? 0 : l(_, o.__wbindgen_malloc, o.__wbindgen_realloc), s = f;
|
||||
u().setInt32(e + 4 * 1, s, true), u().setInt32(e + 4 * 0, r, true);
|
||||
}
|
||||
__name(z, "z");
|
||||
function L(e, t) {
|
||||
throw new Error(m(e, t));
|
||||
}
|
||||
__name(L, "L");
|
||||
function P(e) {
|
||||
return Object.entries(e);
|
||||
}
|
||||
__name(P, "P");
|
||||
function Q(e) {
|
||||
return e.getTime();
|
||||
}
|
||||
__name(Q, "Q");
|
||||
function Y(e, t) {
|
||||
return e[t >>> 0];
|
||||
}
|
||||
__name(Y, "Y");
|
||||
function G(e, t) {
|
||||
return e[t];
|
||||
}
|
||||
__name(G, "G");
|
||||
function J(e) {
|
||||
let t;
|
||||
try {
|
||||
t = e instanceof ArrayBuffer;
|
||||
} catch {
|
||||
t = false;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
__name(J, "J");
|
||||
function X(e) {
|
||||
let t;
|
||||
try {
|
||||
t = e instanceof Uint8Array;
|
||||
} catch {
|
||||
t = false;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
__name(X, "X");
|
||||
function H(e) {
|
||||
return Number.isSafeInteger(e);
|
||||
}
|
||||
__name(H, "H");
|
||||
function K(e) {
|
||||
return e.length;
|
||||
}
|
||||
__name(K, "K");
|
||||
function Z(e) {
|
||||
return e.length;
|
||||
}
|
||||
__name(Z, "Z");
|
||||
function v() {
|
||||
return /* @__PURE__ */ new Date();
|
||||
}
|
||||
__name(v, "v");
|
||||
function ee() {
|
||||
return new Object();
|
||||
}
|
||||
__name(ee, "ee");
|
||||
function te(e) {
|
||||
return new Uint8Array(e);
|
||||
}
|
||||
__name(te, "te");
|
||||
function ne() {
|
||||
return /* @__PURE__ */ new Map();
|
||||
}
|
||||
__name(ne, "ne");
|
||||
function re() {
|
||||
return new Array();
|
||||
}
|
||||
__name(re, "re");
|
||||
function _e(e, t, n) {
|
||||
Uint8Array.prototype.set.call(M(e, t), n);
|
||||
}
|
||||
__name(_e, "_e");
|
||||
function oe(e, t, n) {
|
||||
e[t] = n;
|
||||
}
|
||||
__name(oe, "oe");
|
||||
function ce(e, t, n) {
|
||||
return e.set(t, n);
|
||||
}
|
||||
__name(ce, "ce");
|
||||
function ie(e, t, n) {
|
||||
e[t >>> 0] = n;
|
||||
}
|
||||
__name(ie, "ie");
|
||||
function se(e, t) {
|
||||
global.PRISMA_WASM_PANIC_REGISTRY.set_message(m(e, t));
|
||||
}
|
||||
__name(se, "se");
|
||||
function ue(e, t) {
|
||||
return m(e, t);
|
||||
}
|
||||
__name(ue, "ue");
|
||||
function fe(e) {
|
||||
return BigInt.asUintN(64, e);
|
||||
}
|
||||
__name(fe, "fe");
|
||||
function be(e) {
|
||||
return e;
|
||||
}
|
||||
__name(be, "be");
|
||||
function de(e) {
|
||||
return e;
|
||||
}
|
||||
__name(de, "de");
|
||||
function ae() {
|
||||
const e = o.__wbindgen_externrefs, t = e.grow(4);
|
||||
e.set(0, void 0), e.set(t + 0, void 0), e.set(t + 1, null), e.set(t + 2, true), e.set(t + 3, false);
|
||||
}
|
||||
__name(ae, "ae");
|
||||
export {
|
||||
F as QueryCompiler,
|
||||
O as __wbg_Error_e83987f665cf5504,
|
||||
B as __wbg_Number_bb48ca12f395cd08,
|
||||
N as __wbg_String_8f0eb39a4a4c2f66,
|
||||
U as __wbg___wbindgen_boolean_get_6d5a1ee65bab5f68,
|
||||
R as __wbg___wbindgen_debug_string_df47ffb5e35e6763,
|
||||
$ as __wbg___wbindgen_in_bb933bd9e1b3bc0f,
|
||||
q as __wbg___wbindgen_is_object_c818261d21f283a4,
|
||||
C as __wbg___wbindgen_is_string_fbb76cb2940daafd,
|
||||
k as __wbg___wbindgen_is_undefined_2d472862bd29a478,
|
||||
W as __wbg___wbindgen_jsval_loose_eq_b664b38a2f582147,
|
||||
V as __wbg___wbindgen_number_get_a20bf9b85341449d,
|
||||
z as __wbg___wbindgen_string_get_e4f06c90489ad01b,
|
||||
L as __wbg___wbindgen_throw_b855445ff6a94295,
|
||||
P as __wbg_entries_e171b586f8f6bdbf,
|
||||
Q as __wbg_getTime_14776bfb48a1bff9,
|
||||
Y as __wbg_get_7bed016f185add81,
|
||||
G as __wbg_get_with_ref_key_1dc361bd10053bfe,
|
||||
J as __wbg_instanceof_ArrayBuffer_70beb1189ca63b38,
|
||||
X as __wbg_instanceof_Uint8Array_20c8e73002f7af98,
|
||||
H as __wbg_isSafeInteger_d216eda7911dde36,
|
||||
K as __wbg_length_69bca3cb64fc8748,
|
||||
Z as __wbg_length_cdd215e10d9dd507,
|
||||
v as __wbg_new_0_f9740686d739025c,
|
||||
ee as __wbg_new_1acc0b6eea89d040,
|
||||
te as __wbg_new_5a79be3ab53b8aa5,
|
||||
ne as __wbg_new_68651c719dcda04e,
|
||||
re as __wbg_new_e17d9f43105b08be,
|
||||
_e as __wbg_prototypesetcall_2a6620b6922694b2,
|
||||
oe as __wbg_set_3f1d0b984ed272ed,
|
||||
ce as __wbg_set_907fb406c34a251d,
|
||||
ie as __wbg_set_c213c871859d6500,
|
||||
se as __wbg_set_message_82ae475bb413aa5c,
|
||||
D as __wbg_set_wasm,
|
||||
ue as __wbindgen_cast_2241b6af4c4b2941,
|
||||
fe as __wbindgen_cast_4625c577ab2ec9ee,
|
||||
be as __wbindgen_cast_9ae0607507abb057,
|
||||
de as __wbindgen_cast_d6cd19b81560fd6e,
|
||||
ae as __wbindgen_init_externref_table
|
||||
};
|
||||
+8
File diff suppressed because one or more lines are too long
+4507
File diff suppressed because it is too large
Load Diff
Vendored
+3595
File diff suppressed because it is too large
Load Diff
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
|
||||
import {
|
||||
esm_default
|
||||
} from "./chunks/chunk-KIUJQ22G.js";
|
||||
import {
|
||||
__name
|
||||
} from "./chunks/chunk-T6GW54FI.js";
|
||||
|
||||
// dist/migrate.js
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { existsSync } from "node:fs";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
if (existsSync(".env"))
|
||||
process.loadEnvFile(".env");
|
||||
var MIGRATIONS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../prisma/migrations");
|
||||
var LOCK_ID = 72707369;
|
||||
var client = new esm_default.Client({ connectionString: process.env.DATABASE_URL });
|
||||
async function main() {
|
||||
await client.query(`SELECT pg_advisory_lock(${LOCK_ID})`);
|
||||
await client.query(`CREATE TABLE IF NOT EXISTS "_prisma_migrations" (
|
||||
"id" VARCHAR(36) PRIMARY KEY NOT NULL,
|
||||
"checksum" VARCHAR(64) NOT NULL,
|
||||
"finished_at" TIMESTAMPTZ,
|
||||
"migration_name" VARCHAR(255) NOT NULL,
|
||||
"logs" TEXT,
|
||||
"rolled_back_at" TIMESTAMPTZ,
|
||||
"started_at" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
"applied_steps_count" INTEGER NOT NULL DEFAULT 0
|
||||
)`);
|
||||
const { rows } = await client.query(`SELECT migration_name, finished_at FROM "_prisma_migrations" WHERE rolled_back_at IS NULL`);
|
||||
const failed = rows.find((r) => !r.finished_at);
|
||||
if (failed) {
|
||||
throw new Error(`migration ${failed.migration_name} failed earlier; fix the database, then mark it with \`prisma migrate resolve\` (see its "logs" column in _prisma_migrations)`);
|
||||
}
|
||||
const applied = new Set(rows.map((r) => r.migration_name));
|
||||
const entries = await readdir(MIGRATIONS_DIR, { withFileTypes: true });
|
||||
const pending = entries.filter((e) => e.isDirectory() && !applied.has(e.name)).map((e) => e.name).sort();
|
||||
for (const name of pending) {
|
||||
const script = await readFile(path.join(MIGRATIONS_DIR, name, "migration.sql"), "utf8");
|
||||
const id = randomUUID();
|
||||
await client.query(`INSERT INTO "_prisma_migrations" (id, checksum, migration_name) VALUES ($1, $2, $3)`, [id, createHash("sha256").update(script).digest("hex"), name]);
|
||||
try {
|
||||
await client.query(script);
|
||||
} catch (e) {
|
||||
await client.query(`UPDATE "_prisma_migrations" SET logs = $2 WHERE id = $1`, [
|
||||
id,
|
||||
String(e)
|
||||
]);
|
||||
throw new Error(`migration ${name} failed`, { cause: e });
|
||||
}
|
||||
await client.query(`UPDATE "_prisma_migrations" SET finished_at = now(), applied_steps_count = 1 WHERE id = $1`, [id]);
|
||||
console.log(`applied ${name}`);
|
||||
}
|
||||
console.log(pending.length ? `${pending.length} migration(s) applied` : "no pending migrations");
|
||||
}
|
||||
__name(main, "main");
|
||||
try {
|
||||
await client.connect();
|
||||
await main();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
Vendored
+1460
File diff suppressed because it is too large
Load Diff
+82
@@ -0,0 +1,82 @@
|
||||
# Licensing
|
||||
|
||||
## color
|
||||
|
||||
Copyright (c) 2012 Heather Arthur
|
||||
|
||||
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.
|
||||
|
||||
## color-convert
|
||||
|
||||
Copyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>.
|
||||
Copyright (c) 2016-2021 Josh Junon <josh@junon.me>.
|
||||
|
||||
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.
|
||||
|
||||
## color-string
|
||||
|
||||
Copyright (c) 2011 Heather Arthur <fayearthur@gmail.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.
|
||||
|
||||
## color-name
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2015 Dmitry Ivanov
|
||||
|
||||
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.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# `@img/colour`
|
||||
|
||||
The latest version of the
|
||||
[color](https://www.npmjs.com/package/color)
|
||||
package is now ESM-only,
|
||||
however some JavaScript runtimes do not yet support this,
|
||||
which includes versions of Node.js prior to 20.19.0.
|
||||
|
||||
This package converts the `color` package and its dependencies,
|
||||
all of which are MIT-licensed, to CommonJS.
|
||||
|
||||
- [color](https://www.npmjs.com/package/color)
|
||||
- [color-convert](https://www.npmjs.com/package/color-convert)
|
||||
- [color-string](https://www.npmjs.com/package/color-string)
|
||||
- [color-name](https://www.npmjs.com/package/color-name)
|
||||
+1596
File diff suppressed because it is too large
Load Diff
+1
@@ -0,0 +1 @@
|
||||
module.exports = require("./color.cjs").default;
|
||||
+929
@@ -0,0 +1,929 @@
|
||||
// Generated by dts-bundle-generator v9.5.1
|
||||
|
||||
type Channels = number;
|
||||
type RGB = [
|
||||
r: number,
|
||||
g: number,
|
||||
b: number
|
||||
];
|
||||
type HSL = [
|
||||
h: number,
|
||||
s: number,
|
||||
l: number
|
||||
];
|
||||
type HSV = [
|
||||
h: number,
|
||||
s: number,
|
||||
v: number
|
||||
];
|
||||
type CMYK = [
|
||||
c: number,
|
||||
m: number,
|
||||
y: number,
|
||||
k: number
|
||||
];
|
||||
type LAB = [
|
||||
l: number,
|
||||
a: number,
|
||||
b: number
|
||||
];
|
||||
type LCH = [
|
||||
l: number,
|
||||
c: number,
|
||||
h: number
|
||||
];
|
||||
type HCG = [
|
||||
h: number,
|
||||
c: number,
|
||||
g: number
|
||||
];
|
||||
type HWB = [
|
||||
h: number,
|
||||
w: number,
|
||||
b: number
|
||||
];
|
||||
type XYZ = [
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
];
|
||||
type Apple = [
|
||||
r16: number,
|
||||
g16: number,
|
||||
b16: number
|
||||
];
|
||||
type Gray = [
|
||||
gray: number
|
||||
];
|
||||
type ANSI16 = number;
|
||||
type ANSI256 = number;
|
||||
type Keyword = string;
|
||||
type HEX = string;
|
||||
declare namespace route {
|
||||
type rgb = {
|
||||
hsl(from: RGB): HSL;
|
||||
hsl(...from: RGB): HSL;
|
||||
hsl(from: RGB): HSL;
|
||||
hsl(...from: RGB): HSL;
|
||||
hsv(from: RGB): HSV;
|
||||
hsv(...from: RGB): HSV;
|
||||
hwb(from: RGB): HWB;
|
||||
hwb(...from: RGB): HWB;
|
||||
cmyk(from: RGB): CMYK;
|
||||
cmyk(...from: RGB): CMYK;
|
||||
xyz(from: RGB): XYZ;
|
||||
xyz(...from: RGB): XYZ;
|
||||
lab(from: RGB): LAB;
|
||||
lab(...from: RGB): LAB;
|
||||
lch(from: RGB): LCH;
|
||||
lch(...from: RGB): LCH;
|
||||
hex(from: RGB): HEX;
|
||||
hex(...from: RGB): HEX;
|
||||
keyword(from: RGB): Keyword;
|
||||
keyword(...from: RGB): Keyword;
|
||||
ansi16(from: RGB): ANSI16;
|
||||
ansi16(...from: RGB): ANSI16;
|
||||
ansi256(from: RGB): ANSI256;
|
||||
ansi256(...from: RGB): ANSI256;
|
||||
hcg(from: RGB): HCG;
|
||||
hcg(...from: RGB): HCG;
|
||||
apple(from: RGB): Apple;
|
||||
apple(...from: RGB): Apple;
|
||||
gray(from: RGB): Gray;
|
||||
gray(...from: RGB): Gray;
|
||||
};
|
||||
type hsl = {
|
||||
rgb(from: HSL): RGB;
|
||||
rgb(...from: HSL): RGB;
|
||||
hsv(from: HSL): HSV;
|
||||
hsv(...from: HSL): HSV;
|
||||
hwb(from: HSL): HWB;
|
||||
hwb(...from: HSL): HWB;
|
||||
cmyk(from: HSL): CMYK;
|
||||
cmyk(...from: HSL): CMYK;
|
||||
xyz(from: HSL): XYZ;
|
||||
xyz(...from: HSL): XYZ;
|
||||
lab(from: HSL): LAB;
|
||||
lab(...from: HSL): LAB;
|
||||
lch(from: HSL): LCH;
|
||||
lch(...from: HSL): LCH;
|
||||
hex(from: HSL): HEX;
|
||||
hex(...from: HSL): HEX;
|
||||
keyword(from: HSL): Keyword;
|
||||
keyword(...from: HSL): Keyword;
|
||||
ansi16(from: HSL): ANSI16;
|
||||
ansi16(...from: HSL): ANSI16;
|
||||
ansi256(from: HSL): ANSI256;
|
||||
ansi256(...from: HSL): ANSI256;
|
||||
hcg(from: HSL): HCG;
|
||||
hcg(...from: HSL): HCG;
|
||||
apple(from: HSL): Apple;
|
||||
apple(...from: HSL): Apple;
|
||||
gray(from: HSL): Gray;
|
||||
gray(...from: HSL): Gray;
|
||||
};
|
||||
type hsv = {
|
||||
rgb(from: HSV): RGB;
|
||||
rgb(...from: HSV): RGB;
|
||||
hsl(from: HSV): HSL;
|
||||
hsl(...from: HSV): HSL;
|
||||
hwb(from: HSV): HWB;
|
||||
hwb(...from: HSV): HWB;
|
||||
cmyk(from: HSV): CMYK;
|
||||
cmyk(...from: HSV): CMYK;
|
||||
xyz(from: HSV): XYZ;
|
||||
xyz(...from: HSV): XYZ;
|
||||
lab(from: HSV): LAB;
|
||||
lab(...from: HSV): LAB;
|
||||
lch(from: HSV): LCH;
|
||||
lch(...from: HSV): LCH;
|
||||
hex(from: HSV): HEX;
|
||||
hex(...from: HSV): HEX;
|
||||
keyword(from: HSV): Keyword;
|
||||
keyword(...from: HSV): Keyword;
|
||||
ansi16(from: HSV): ANSI16;
|
||||
ansi16(...from: HSV): ANSI16;
|
||||
ansi256(from: HSV): ANSI256;
|
||||
ansi256(...from: HSV): ANSI256;
|
||||
hcg(from: HSV): HCG;
|
||||
hcg(...from: HSV): HCG;
|
||||
apple(from: HSV): Apple;
|
||||
apple(...from: HSV): Apple;
|
||||
gray(from: HSV): Gray;
|
||||
gray(...from: HSV): Gray;
|
||||
};
|
||||
type hwb = {
|
||||
rgb(from: HWB): RGB;
|
||||
rgb(...from: HWB): RGB;
|
||||
hsl(from: HWB): HSL;
|
||||
hsl(...from: HWB): HSL;
|
||||
hsv(from: HWB): HSV;
|
||||
hsv(...from: HWB): HSV;
|
||||
cmyk(from: HWB): CMYK;
|
||||
cmyk(...from: HWB): CMYK;
|
||||
xyz(from: HWB): XYZ;
|
||||
xyz(...from: HWB): XYZ;
|
||||
lab(from: HWB): LAB;
|
||||
lab(...from: HWB): LAB;
|
||||
lch(from: HWB): LCH;
|
||||
lch(...from: HWB): LCH;
|
||||
hex(from: HWB): HEX;
|
||||
hex(...from: HWB): HEX;
|
||||
keyword(from: HWB): Keyword;
|
||||
keyword(...from: HWB): Keyword;
|
||||
ansi16(from: HWB): ANSI16;
|
||||
ansi16(...from: HWB): ANSI16;
|
||||
ansi256(from: HWB): ANSI256;
|
||||
ansi256(...from: HWB): ANSI256;
|
||||
hcg(from: HWB): HCG;
|
||||
hcg(...from: HWB): HCG;
|
||||
apple(from: HWB): Apple;
|
||||
apple(...from: HWB): Apple;
|
||||
gray(from: HWB): Gray;
|
||||
gray(...from: HWB): Gray;
|
||||
};
|
||||
type cmyk = {
|
||||
rgb(from: CMYK): RGB;
|
||||
rgb(...from: CMYK): RGB;
|
||||
hsl(from: CMYK): HSL;
|
||||
hsl(...from: CMYK): HSL;
|
||||
hsv(from: CMYK): HSV;
|
||||
hsv(...from: CMYK): HSV;
|
||||
hwb(from: CMYK): HWB;
|
||||
hwb(...from: CMYK): HWB;
|
||||
xyz(from: CMYK): XYZ;
|
||||
xyz(...from: CMYK): XYZ;
|
||||
lab(from: CMYK): LAB;
|
||||
lab(...from: CMYK): LAB;
|
||||
lch(from: CMYK): LCH;
|
||||
lch(...from: CMYK): LCH;
|
||||
hex(from: CMYK): HEX;
|
||||
hex(...from: CMYK): HEX;
|
||||
keyword(from: CMYK): Keyword;
|
||||
keyword(...from: CMYK): Keyword;
|
||||
ansi16(from: CMYK): ANSI16;
|
||||
ansi16(...from: CMYK): ANSI16;
|
||||
ansi256(from: CMYK): ANSI256;
|
||||
ansi256(...from: CMYK): ANSI256;
|
||||
hcg(from: CMYK): HCG;
|
||||
hcg(...from: CMYK): HCG;
|
||||
apple(from: CMYK): Apple;
|
||||
apple(...from: CMYK): Apple;
|
||||
gray(from: CMYK): Gray;
|
||||
gray(...from: CMYK): Gray;
|
||||
};
|
||||
type xyz = {
|
||||
rgb(from: XYZ): RGB;
|
||||
rgb(...from: XYZ): RGB;
|
||||
hsl(from: XYZ): HSL;
|
||||
hsl(...from: XYZ): HSL;
|
||||
hsv(from: XYZ): HSV;
|
||||
hsv(...from: XYZ): HSV;
|
||||
hwb(from: XYZ): HWB;
|
||||
hwb(...from: XYZ): HWB;
|
||||
cmyk(from: XYZ): CMYK;
|
||||
cmyk(...from: XYZ): CMYK;
|
||||
lab(from: XYZ): LAB;
|
||||
lab(...from: XYZ): LAB;
|
||||
lch(from: XYZ): LCH;
|
||||
lch(...from: XYZ): LCH;
|
||||
hex(from: XYZ): HEX;
|
||||
hex(...from: XYZ): HEX;
|
||||
keyword(from: XYZ): Keyword;
|
||||
keyword(...from: XYZ): Keyword;
|
||||
ansi16(from: XYZ): ANSI16;
|
||||
ansi16(...from: XYZ): ANSI16;
|
||||
ansi256(from: XYZ): ANSI256;
|
||||
ansi256(...from: XYZ): ANSI256;
|
||||
hcg(from: XYZ): HCG;
|
||||
hcg(...from: XYZ): HCG;
|
||||
apple(from: XYZ): Apple;
|
||||
apple(...from: XYZ): Apple;
|
||||
gray(from: XYZ): Gray;
|
||||
gray(...from: XYZ): Gray;
|
||||
};
|
||||
type lab = {
|
||||
rgb(from: LAB): RGB;
|
||||
rgb(...from: LAB): RGB;
|
||||
hsl(from: LAB): HSL;
|
||||
hsl(...from: LAB): HSL;
|
||||
hsv(from: LAB): HSV;
|
||||
hsv(...from: LAB): HSV;
|
||||
hwb(from: LAB): HWB;
|
||||
hwb(...from: LAB): HWB;
|
||||
cmyk(from: LAB): CMYK;
|
||||
cmyk(...from: LAB): CMYK;
|
||||
xyz(from: LAB): XYZ;
|
||||
xyz(...from: LAB): XYZ;
|
||||
lch(from: LAB): LCH;
|
||||
lch(...from: LAB): LCH;
|
||||
hex(from: LAB): HEX;
|
||||
hex(...from: LAB): HEX;
|
||||
keyword(from: LAB): Keyword;
|
||||
keyword(...from: LAB): Keyword;
|
||||
ansi16(from: LAB): ANSI16;
|
||||
ansi16(...from: LAB): ANSI16;
|
||||
ansi256(from: LAB): ANSI256;
|
||||
ansi256(...from: LAB): ANSI256;
|
||||
hcg(from: LAB): HCG;
|
||||
hcg(...from: LAB): HCG;
|
||||
apple(from: LAB): Apple;
|
||||
apple(...from: LAB): Apple;
|
||||
gray(from: LAB): Gray;
|
||||
gray(...from: LAB): Gray;
|
||||
};
|
||||
type lch = {
|
||||
rgb(from: LCH): RGB;
|
||||
rgb(...from: LCH): RGB;
|
||||
hsl(from: LCH): HSL;
|
||||
hsl(...from: LCH): HSL;
|
||||
hsv(from: LCH): HSV;
|
||||
hsv(...from: LCH): HSV;
|
||||
hwb(from: LCH): HWB;
|
||||
hwb(...from: LCH): HWB;
|
||||
cmyk(from: LCH): CMYK;
|
||||
cmyk(...from: LCH): CMYK;
|
||||
xyz(from: LCH): XYZ;
|
||||
xyz(...from: LCH): XYZ;
|
||||
lab(from: LCH): LAB;
|
||||
lab(...from: LCH): LAB;
|
||||
hex(from: LCH): HEX;
|
||||
hex(...from: LCH): HEX;
|
||||
keyword(from: LCH): Keyword;
|
||||
keyword(...from: LCH): Keyword;
|
||||
ansi16(from: LCH): ANSI16;
|
||||
ansi16(...from: LCH): ANSI16;
|
||||
ansi256(from: LCH): ANSI256;
|
||||
ansi256(...from: LCH): ANSI256;
|
||||
hcg(from: LCH): HCG;
|
||||
hcg(...from: LCH): HCG;
|
||||
apple(from: LCH): Apple;
|
||||
apple(...from: LCH): Apple;
|
||||
gray(from: LCH): Gray;
|
||||
gray(...from: LCH): Gray;
|
||||
};
|
||||
type hex = {
|
||||
rgb(from: HEX): RGB;
|
||||
hsl(from: HEX): HSL;
|
||||
hsv(from: HEX): HSV;
|
||||
hwb(from: HEX): HWB;
|
||||
cmyk(from: HEX): CMYK;
|
||||
xyz(from: HEX): XYZ;
|
||||
lab(from: HEX): LAB;
|
||||
lch(from: HEX): LCH;
|
||||
keyword(from: HEX): Keyword;
|
||||
ansi16(from: HEX): ANSI16;
|
||||
ansi256(from: HEX): ANSI256;
|
||||
hcg(from: HEX): HCG;
|
||||
apple(from: HEX): Apple;
|
||||
gray(from: HEX): Gray;
|
||||
};
|
||||
type keyword = {
|
||||
rgb(from: Keyword): RGB;
|
||||
hsl(from: Keyword): HSL;
|
||||
hsv(from: Keyword): HSV;
|
||||
hwb(from: Keyword): HWB;
|
||||
cmyk(from: Keyword): CMYK;
|
||||
xyz(from: Keyword): XYZ;
|
||||
lab(from: Keyword): LAB;
|
||||
lch(from: Keyword): LCH;
|
||||
hex(from: Keyword): HEX;
|
||||
ansi16(from: Keyword): ANSI16;
|
||||
ansi256(from: Keyword): ANSI256;
|
||||
hcg(from: Keyword): HCG;
|
||||
apple(from: Keyword): Apple;
|
||||
gray(from: Keyword): Gray;
|
||||
};
|
||||
type ansi16 = {
|
||||
rgb(from: ANSI16): RGB;
|
||||
hsl(from: ANSI16): HSL;
|
||||
hsv(from: ANSI16): HSV;
|
||||
hwb(from: ANSI16): HWB;
|
||||
cmyk(from: ANSI16): CMYK;
|
||||
xyz(from: ANSI16): XYZ;
|
||||
lab(from: ANSI16): LAB;
|
||||
lch(from: ANSI16): LCH;
|
||||
hex(from: ANSI16): HEX;
|
||||
keyword(from: ANSI16): Keyword;
|
||||
ansi256(from: ANSI16): ANSI256;
|
||||
hcg(from: ANSI16): HCG;
|
||||
apple(from: ANSI16): Apple;
|
||||
gray(from: ANSI16): Gray;
|
||||
};
|
||||
type ansi256 = {
|
||||
rgb(from: ANSI256): RGB;
|
||||
hsl(from: ANSI256): HSL;
|
||||
hsv(from: ANSI256): HSV;
|
||||
hwb(from: ANSI256): HWB;
|
||||
cmyk(from: ANSI256): CMYK;
|
||||
xyz(from: ANSI256): XYZ;
|
||||
lab(from: ANSI256): LAB;
|
||||
lch(from: ANSI256): LCH;
|
||||
hex(from: ANSI256): HEX;
|
||||
keyword(from: ANSI256): Keyword;
|
||||
ansi16(from: ANSI256): ANSI16;
|
||||
hcg(from: ANSI256): HCG;
|
||||
apple(from: ANSI256): Apple;
|
||||
gray(from: ANSI256): Gray;
|
||||
};
|
||||
type hcg = {
|
||||
rgb(from: HCG): RGB;
|
||||
rgb(...from: HCG): RGB;
|
||||
hsl(from: HCG): HSL;
|
||||
hsl(...from: HCG): HSL;
|
||||
hsv(from: HCG): HSV;
|
||||
hsv(...from: HCG): HSV;
|
||||
hwb(from: HCG): HWB;
|
||||
hwb(...from: HCG): HWB;
|
||||
cmyk(from: HCG): CMYK;
|
||||
cmyk(...from: HCG): CMYK;
|
||||
xyz(from: HCG): XYZ;
|
||||
xyz(...from: HCG): XYZ;
|
||||
lab(from: HCG): LAB;
|
||||
lab(...from: HCG): LAB;
|
||||
lch(from: HCG): LCH;
|
||||
lch(...from: HCG): LCH;
|
||||
hex(from: HCG): HEX;
|
||||
hex(...from: HCG): HEX;
|
||||
keyword(from: HCG): Keyword;
|
||||
keyword(...from: HCG): Keyword;
|
||||
ansi16(from: HCG): ANSI16;
|
||||
ansi16(...from: HCG): ANSI16;
|
||||
ansi256(from: HCG): ANSI256;
|
||||
ansi256(...from: HCG): ANSI256;
|
||||
apple(from: HCG): Apple;
|
||||
apple(...from: HCG): Apple;
|
||||
gray(from: HCG): Gray;
|
||||
gray(...from: HCG): Gray;
|
||||
};
|
||||
type apple = {
|
||||
rgb(from: Apple): RGB;
|
||||
rgb(...from: Apple): RGB;
|
||||
hsl(from: Apple): HSL;
|
||||
hsl(...from: Apple): HSL;
|
||||
hsv(from: Apple): HSV;
|
||||
hsv(...from: Apple): HSV;
|
||||
hwb(from: Apple): HWB;
|
||||
hwb(...from: Apple): HWB;
|
||||
cmyk(from: Apple): CMYK;
|
||||
cmyk(...from: Apple): CMYK;
|
||||
xyz(from: Apple): XYZ;
|
||||
xyz(...from: Apple): XYZ;
|
||||
lab(from: Apple): LAB;
|
||||
lab(...from: Apple): LAB;
|
||||
lch(from: Apple): LCH;
|
||||
lch(...from: Apple): LCH;
|
||||
hex(from: Apple): HEX;
|
||||
hex(...from: Apple): HEX;
|
||||
keyword(from: Apple): Keyword;
|
||||
keyword(...from: Apple): Keyword;
|
||||
ansi16(from: Apple): ANSI16;
|
||||
ansi16(...from: Apple): ANSI16;
|
||||
ansi256(from: Apple): ANSI256;
|
||||
ansi256(...from: Apple): ANSI256;
|
||||
hcg(from: Apple): HCG;
|
||||
hcg(...from: Apple): HCG;
|
||||
gray(from: Apple): Gray;
|
||||
gray(...from: Apple): Gray;
|
||||
};
|
||||
type gray = {
|
||||
rgb(from: Gray): RGB;
|
||||
rgb(...from: Gray): RGB;
|
||||
hsl(from: Gray): HSL;
|
||||
hsl(...from: Gray): HSL;
|
||||
hsv(from: Gray): HSV;
|
||||
hsv(...from: Gray): HSV;
|
||||
hwb(from: Gray): HWB;
|
||||
hwb(...from: Gray): HWB;
|
||||
cmyk(from: Gray): CMYK;
|
||||
cmyk(...from: Gray): CMYK;
|
||||
xyz(from: Gray): XYZ;
|
||||
xyz(...from: Gray): XYZ;
|
||||
lab(from: Gray): LAB;
|
||||
lab(...from: Gray): LAB;
|
||||
lch(from: Gray): LCH;
|
||||
lch(...from: Gray): LCH;
|
||||
hex(from: Gray): HEX;
|
||||
hex(...from: Gray): HEX;
|
||||
keyword(from: Gray): Keyword;
|
||||
keyword(...from: Gray): Keyword;
|
||||
ansi16(from: Gray): ANSI16;
|
||||
ansi16(...from: Gray): ANSI16;
|
||||
ansi256(from: Gray): ANSI256;
|
||||
ansi256(...from: Gray): ANSI256;
|
||||
hcg(from: Gray): HCG;
|
||||
hcg(...from: Gray): HCG;
|
||||
apple(from: Gray): Apple;
|
||||
apple(...from: Gray): Apple;
|
||||
};
|
||||
}
|
||||
declare function route(fromModel: "rgb"): route.rgb;
|
||||
declare function route(fromModel: "hsl"): route.hsl;
|
||||
declare function route(fromModel: "hsv"): route.hsv;
|
||||
declare function route(fromModel: "hwb"): route.hwb;
|
||||
declare function route(fromModel: "cmyk"): route.cmyk;
|
||||
declare function route(fromModel: "xyz"): route.xyz;
|
||||
declare function route(fromModel: "lab"): route.lab;
|
||||
declare function route(fromModel: "lch"): route.lch;
|
||||
declare function route(fromModel: "hex"): route.hex;
|
||||
declare function route(fromModel: "keyword"): route.keyword;
|
||||
declare function route(fromModel: "ansi16"): route.ansi16;
|
||||
declare function route(fromModel: "ansi256"): route.ansi256;
|
||||
declare function route(fromModel: "hcg"): route.hcg;
|
||||
declare function route(fromModel: "apple"): route.apple;
|
||||
declare function route(fromModel: "gray"): route.gray;
|
||||
type Convert = {
|
||||
rgb: {
|
||||
channels: Channels;
|
||||
labels: "rgb";
|
||||
hsl: {
|
||||
(...rgb: RGB): HSL;
|
||||
raw: (...rgb: RGB) => HSL;
|
||||
};
|
||||
hsv: {
|
||||
(...rgb: RGB): HSV;
|
||||
raw: (...rgb: RGB) => HSV;
|
||||
};
|
||||
hwb: {
|
||||
(...rgb: RGB): HWB;
|
||||
raw: (...rgb: RGB) => HWB;
|
||||
};
|
||||
hcg: {
|
||||
(...rgb: RGB): HCG;
|
||||
raw: (...rgb: RGB) => HCG;
|
||||
};
|
||||
cmyk: {
|
||||
(...rgb: RGB): CMYK;
|
||||
raw: (...rgb: RGB) => CMYK;
|
||||
};
|
||||
keyword: {
|
||||
(...rgb: RGB): Keyword;
|
||||
raw: (...rgb: RGB) => Keyword;
|
||||
};
|
||||
ansi16: {
|
||||
(...rgb: RGB): ANSI16;
|
||||
raw: (...rgb: RGB) => ANSI16;
|
||||
};
|
||||
ansi256: {
|
||||
(...rgb: RGB): ANSI256;
|
||||
raw: (...rgb: RGB) => ANSI256;
|
||||
};
|
||||
apple: {
|
||||
(...rgb: RGB): Apple;
|
||||
raw: (...rgb: RGB) => Apple;
|
||||
};
|
||||
hex: {
|
||||
(...rgb: RGB): HEX;
|
||||
raw: (...rgb: RGB) => HEX;
|
||||
};
|
||||
gray: {
|
||||
(...rgb: RGB): Gray;
|
||||
raw: (...rgb: RGB) => Gray;
|
||||
};
|
||||
} & route.rgb & {
|
||||
[F in keyof route.rgb]: {
|
||||
raw: route.rgb[F];
|
||||
};
|
||||
};
|
||||
keyword: {
|
||||
channels: Channels;
|
||||
rgb: {
|
||||
(keyword: Keyword): RGB;
|
||||
raw: (keyword: Keyword) => RGB;
|
||||
};
|
||||
} & route.keyword & {
|
||||
[F in keyof route.keyword]: {
|
||||
raw: route.keyword[F];
|
||||
};
|
||||
};
|
||||
hsl: {
|
||||
channels: Channels;
|
||||
labels: "hsl";
|
||||
rgb: {
|
||||
(...hsl: HSL): RGB;
|
||||
raw: (...hsl: HSL) => RGB;
|
||||
};
|
||||
hsv: {
|
||||
(...hsl: HSL): HSV;
|
||||
raw: (...hsl: HSL) => HSV;
|
||||
};
|
||||
hcg: {
|
||||
(...hsl: HSL): HCG;
|
||||
raw: (...hsl: HSL) => HCG;
|
||||
};
|
||||
} & route.hsl & {
|
||||
[F in keyof route.hsl]: {
|
||||
raw: route.hsl[F];
|
||||
};
|
||||
};
|
||||
hsv: {
|
||||
channels: Channels;
|
||||
labels: "hsv";
|
||||
hcg: {
|
||||
(...hsv: HSV): HCG;
|
||||
raw: (...hsv: HSV) => HCG;
|
||||
};
|
||||
rgb: {
|
||||
(...hsv: HSV): RGB;
|
||||
raw: (...hsv: HSV) => RGB;
|
||||
};
|
||||
hsv: {
|
||||
(...hsv: HSV): HSV;
|
||||
raw: (...hsv: HSV) => HSV;
|
||||
};
|
||||
hsl: {
|
||||
(...hsv: HSV): HSL;
|
||||
raw: (...hsv: HSV) => HSL;
|
||||
};
|
||||
hwb: {
|
||||
(...hsv: HSV): HWB;
|
||||
raw: (...hsv: HSV) => HWB;
|
||||
};
|
||||
ansi16: {
|
||||
(...hsv: HSV): ANSI16;
|
||||
raw: (...hsv: HSV) => ANSI16;
|
||||
};
|
||||
} & route.hsv & {
|
||||
[F in keyof route.hsv]: {
|
||||
raw: route.hsv[F];
|
||||
};
|
||||
};
|
||||
hwb: {
|
||||
channels: Channels;
|
||||
labels: "hwb";
|
||||
hcg: {
|
||||
(...hwb: HWB): HCG;
|
||||
raw: (...hwb: HWB) => HCG;
|
||||
};
|
||||
rgb: {
|
||||
(...hwb: HWB): RGB;
|
||||
raw: (...hwb: HWB) => RGB;
|
||||
};
|
||||
} & route.hwb & {
|
||||
[F in keyof route.hwb]: {
|
||||
raw: route.hwb[F];
|
||||
};
|
||||
};
|
||||
cmyk: {
|
||||
channels: Channels;
|
||||
labels: "cmyk";
|
||||
rgb: {
|
||||
(...cmyk: CMYK): RGB;
|
||||
raw: (...cmyk: CMYK) => RGB;
|
||||
};
|
||||
} & route.cmyk & {
|
||||
[F in keyof route.cmyk]: {
|
||||
raw: route.cmyk[F];
|
||||
};
|
||||
};
|
||||
xyz: {
|
||||
channels: Channels;
|
||||
labels: "xyz";
|
||||
rgb: {
|
||||
(...xyz: XYZ): RGB;
|
||||
raw: (...xyz: XYZ) => RGB;
|
||||
};
|
||||
lab: {
|
||||
(...xyz: XYZ): LAB;
|
||||
raw: (...xyz: XYZ) => LAB;
|
||||
};
|
||||
} & route.xyz & {
|
||||
[F in keyof route.xyz]: {
|
||||
raw: route.xyz[F];
|
||||
};
|
||||
};
|
||||
lab: {
|
||||
channels: Channels;
|
||||
labels: "lab";
|
||||
xyz: {
|
||||
(...lab: LAB): XYZ;
|
||||
raw: (...lab: LAB) => XYZ;
|
||||
};
|
||||
lch: {
|
||||
(...lab: LAB): LCH;
|
||||
raw: (...lab: LAB) => LCH;
|
||||
};
|
||||
} & route.lab & {
|
||||
[F in keyof route.lab]: {
|
||||
raw: route.lab[F];
|
||||
};
|
||||
};
|
||||
lch: {
|
||||
channels: Channels;
|
||||
labels: "lch";
|
||||
lab: {
|
||||
(...lch: LCH): LAB;
|
||||
raw: (...lch: LCH) => LAB;
|
||||
};
|
||||
} & route.lch & {
|
||||
[F in keyof route.lch]: {
|
||||
raw: route.lch[F];
|
||||
};
|
||||
};
|
||||
hex: {
|
||||
channels: Channels;
|
||||
labels: [
|
||||
"hex"
|
||||
];
|
||||
rgb: {
|
||||
(hex: HEX): RGB;
|
||||
raw: (hex: HEX) => RGB;
|
||||
};
|
||||
} & route.hex & {
|
||||
[F in keyof route.hex]: {
|
||||
raw: route.hex[F];
|
||||
};
|
||||
};
|
||||
ansi16: {
|
||||
channels: Channels;
|
||||
labels: [
|
||||
"ansi16"
|
||||
];
|
||||
rgb: {
|
||||
(ansi16: ANSI16): RGB;
|
||||
raw: (ansi16: ANSI16) => RGB;
|
||||
};
|
||||
} & route.ansi16 & {
|
||||
[F in keyof route.ansi16]: {
|
||||
raw: route.ansi16[F];
|
||||
};
|
||||
};
|
||||
ansi256: {
|
||||
channels: Channels;
|
||||
labels: [
|
||||
"ansi256"
|
||||
];
|
||||
rgb: {
|
||||
(ansi256: ANSI256): RGB;
|
||||
raw: (ansi256: ANSI256) => RGB;
|
||||
};
|
||||
} & route.ansi256 & {
|
||||
[F in keyof route.ansi256]: {
|
||||
raw: route.ansi256[F];
|
||||
};
|
||||
};
|
||||
hcg: {
|
||||
channels: Channels;
|
||||
labels: [
|
||||
"h",
|
||||
"c",
|
||||
"g"
|
||||
];
|
||||
rgb: {
|
||||
(...hcg: HCG): RGB;
|
||||
raw: (...hcg: HCG) => RGB;
|
||||
};
|
||||
hsv: {
|
||||
(...hcg: HCG): HSV;
|
||||
raw: (...hcg: HCG) => HSV;
|
||||
};
|
||||
hwb: {
|
||||
(...hcg: HCG): HWB;
|
||||
raw: (...hcg: HCG) => HWB;
|
||||
};
|
||||
} & route.hcg & {
|
||||
[F in keyof route.hcg]: {
|
||||
raw: route.hcg[F];
|
||||
};
|
||||
};
|
||||
apple: {
|
||||
channels: Channels;
|
||||
labels: [
|
||||
"r16",
|
||||
"g16",
|
||||
"b16"
|
||||
];
|
||||
rgb: {
|
||||
(...apple: Apple): RGB;
|
||||
raw: (...apple: Apple) => RGB;
|
||||
};
|
||||
} & route.apple & {
|
||||
[F in keyof route.apple]: {
|
||||
raw: route.apple[F];
|
||||
};
|
||||
};
|
||||
gray: {
|
||||
channels: Channels;
|
||||
labels: [
|
||||
"gray"
|
||||
];
|
||||
rgb: {
|
||||
(...gray: Gray): RGB;
|
||||
raw: (...gray: Gray) => RGB;
|
||||
};
|
||||
hsl: {
|
||||
(...gray: Gray): HSL;
|
||||
raw: (...gray: Gray) => HSL;
|
||||
};
|
||||
hsv: {
|
||||
(...gray: Gray): HSV;
|
||||
raw: (...gray: Gray) => HSV;
|
||||
};
|
||||
hwb: {
|
||||
(...gray: Gray): HWB;
|
||||
raw: (...gray: Gray) => HWB;
|
||||
};
|
||||
cmyk: {
|
||||
(...gray: Gray): CMYK;
|
||||
raw: (...gray: Gray) => CMYK;
|
||||
};
|
||||
lab: {
|
||||
(...gray: Gray): LAB;
|
||||
raw: (...gray: Gray) => LAB;
|
||||
};
|
||||
hex: {
|
||||
(...gray: Gray): HEX;
|
||||
raw: (...gray: Gray) => HEX;
|
||||
};
|
||||
} & route.gray & {
|
||||
[F in keyof route.gray]: {
|
||||
raw: route.gray[F];
|
||||
};
|
||||
};
|
||||
};
|
||||
declare const convert: Convert;
|
||||
export type ColorLike = ColorInstance | string | ArrayLike<number> | number | Record<string, any>;
|
||||
export type ColorJson = {
|
||||
model: string;
|
||||
color: number[];
|
||||
valpha: number;
|
||||
};
|
||||
export type ColorObject = {
|
||||
alpha?: number | undefined;
|
||||
} & Record<string, number>;
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export interface ColorInstance {
|
||||
toString(): string;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
toJSON(): ColorJson;
|
||||
string(places?: number): string;
|
||||
percentString(places?: number): string;
|
||||
array(): number[];
|
||||
object(): ColorObject;
|
||||
unitArray(): number[];
|
||||
unitObject(): {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
alpha?: number | undefined;
|
||||
};
|
||||
round(places?: number): ColorInstance;
|
||||
alpha(): number;
|
||||
alpha(value: number): ColorInstance;
|
||||
red(): number;
|
||||
red(value: number): ColorInstance;
|
||||
green(): number;
|
||||
green(value: number): ColorInstance;
|
||||
blue(): number;
|
||||
blue(value: number): ColorInstance;
|
||||
hue(): number;
|
||||
hue(value: number): ColorInstance;
|
||||
saturationl(): number;
|
||||
saturationl(value: number): ColorInstance;
|
||||
lightness(): number;
|
||||
lightness(value: number): ColorInstance;
|
||||
saturationv(): number;
|
||||
saturationv(value: number): ColorInstance;
|
||||
value(): number;
|
||||
value(value: number): ColorInstance;
|
||||
chroma(): number;
|
||||
chroma(value: number): ColorInstance;
|
||||
gray(): number;
|
||||
gray(value: number): ColorInstance;
|
||||
white(): number;
|
||||
white(value: number): ColorInstance;
|
||||
wblack(): number;
|
||||
wblack(value: number): ColorInstance;
|
||||
cyan(): number;
|
||||
cyan(value: number): ColorInstance;
|
||||
magenta(): number;
|
||||
magenta(value: number): ColorInstance;
|
||||
yellow(): number;
|
||||
yellow(value: number): ColorInstance;
|
||||
black(): number;
|
||||
black(value: number): ColorInstance;
|
||||
x(): number;
|
||||
x(value: number): ColorInstance;
|
||||
y(): number;
|
||||
y(value: number): ColorInstance;
|
||||
z(): number;
|
||||
z(value: number): ColorInstance;
|
||||
l(): number;
|
||||
l(value: number): ColorInstance;
|
||||
a(): number;
|
||||
a(value: number): ColorInstance;
|
||||
b(): number;
|
||||
b(value: number): ColorInstance;
|
||||
keyword(): string;
|
||||
keyword<V extends string>(value: V): ColorInstance;
|
||||
hex(): string;
|
||||
hex<V extends string>(value: V): ColorInstance;
|
||||
hexa(): string;
|
||||
hexa<V extends string>(value: V): ColorInstance;
|
||||
rgbNumber(): number;
|
||||
luminosity(): number;
|
||||
contrast(color2: ColorInstance): number;
|
||||
level(color2: ColorInstance): "AAA" | "AA" | "";
|
||||
isDark(): boolean;
|
||||
isLight(): boolean;
|
||||
negate(): ColorInstance;
|
||||
lighten(ratio: number): ColorInstance;
|
||||
darken(ratio: number): ColorInstance;
|
||||
saturate(ratio: number): ColorInstance;
|
||||
desaturate(ratio: number): ColorInstance;
|
||||
whiten(ratio: number): ColorInstance;
|
||||
blacken(ratio: number): ColorInstance;
|
||||
grayscale(): ColorInstance;
|
||||
fade(ratio: number): ColorInstance;
|
||||
opaquer(ratio: number): ColorInstance;
|
||||
rotate(degrees: number): ColorInstance;
|
||||
mix(mixinColor: ColorInstance, weight?: number): ColorInstance;
|
||||
rgb(...arguments_: number[]): ColorInstance;
|
||||
hsl(...arguments_: number[]): ColorInstance;
|
||||
hsv(...arguments_: number[]): ColorInstance;
|
||||
hwb(...arguments_: number[]): ColorInstance;
|
||||
cmyk(...arguments_: number[]): ColorInstance;
|
||||
xyz(...arguments_: number[]): ColorInstance;
|
||||
lab(...arguments_: number[]): ColorInstance;
|
||||
lch(...arguments_: number[]): ColorInstance;
|
||||
ansi16(...arguments_: number[]): ColorInstance;
|
||||
ansi256(...arguments_: number[]): ColorInstance;
|
||||
hcg(...arguments_: number[]): ColorInstance;
|
||||
apple(...arguments_: number[]): ColorInstance;
|
||||
}
|
||||
export type ColorConstructor = {
|
||||
(object?: ColorLike, model?: keyof (typeof convert)): ColorInstance;
|
||||
new (object?: ColorLike, model?: keyof (typeof convert)): ColorInstance;
|
||||
rgb(...value: number[]): ColorInstance;
|
||||
rgb(color: ColorLike): ColorInstance;
|
||||
hsl(...value: number[]): ColorInstance;
|
||||
hsl(color: ColorLike): ColorInstance;
|
||||
hsv(...value: number[]): ColorInstance;
|
||||
hsv(color: ColorLike): ColorInstance;
|
||||
hwb(...value: number[]): ColorInstance;
|
||||
hwb(color: ColorLike): ColorInstance;
|
||||
cmyk(...value: number[]): ColorInstance;
|
||||
cmyk(color: ColorLike): ColorInstance;
|
||||
xyz(...value: number[]): ColorInstance;
|
||||
xyz(color: ColorLike): ColorInstance;
|
||||
lab(...value: number[]): ColorInstance;
|
||||
lab(color: ColorLike): ColorInstance;
|
||||
lch(...value: number[]): ColorInstance;
|
||||
lch(color: ColorLike): ColorInstance;
|
||||
ansi16(...value: number[]): ColorInstance;
|
||||
ansi16(color: ColorLike): ColorInstance;
|
||||
ansi256(...value: number[]): ColorInstance;
|
||||
ansi256(color: ColorLike): ColorInstance;
|
||||
hcg(...value: number[]): ColorInstance;
|
||||
hcg(color: ColorLike): ColorInstance;
|
||||
apple(...value: number[]): ColorInstance;
|
||||
apple(color: ColorLike): ColorInstance;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
declare const Color: ColorConstructor;
|
||||
|
||||
export {
|
||||
Color as default,
|
||||
};
|
||||
|
||||
export {};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@img/colour",
|
||||
"version": "1.1.0",
|
||||
"description": "The ESM-only 'color' package made compatible for use with CommonJS runtimes",
|
||||
"license": "MIT",
|
||||
"main": "index.cjs",
|
||||
"types": "index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index.cjs",
|
||||
"default": "./index.cjs"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"authors": [
|
||||
"Heather Arthur <fayearthur@gmail.com>",
|
||||
"Josh Junon <josh@junon.me>",
|
||||
"Maxime Thirouin",
|
||||
"Dyma Ywanov <dfcreative@gmail.com>",
|
||||
"LitoMore (https://github.com/LitoMore)"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"files": [
|
||||
"color.cjs",
|
||||
"index.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lovell/colour.git"
|
||||
},
|
||||
"type": "commonjs",
|
||||
"keywords": [
|
||||
"color",
|
||||
"colour",
|
||||
"cjs",
|
||||
"commonjs"
|
||||
],
|
||||
"scripts": {
|
||||
"build:cjs": "esbuild node_modules/color/index.js --bundle --platform=node --outfile=color.cjs",
|
||||
"build:dts": "dts-bundle-generator ./dts-src.ts -o index.d.ts --project tsconfig.build.json --external-inlines color --external-inlines color-convert --export-referenced-types=false",
|
||||
"build": "npm run build:cjs && npm run build:dts",
|
||||
"test": "node --test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"color": "5.0.3",
|
||||
"color-convert": "3.1.3",
|
||||
"color-name": "2.1.0",
|
||||
"color-string": "2.1.4",
|
||||
"dts-bundle-generator": "^9.5.1",
|
||||
"esbuild": "^0.27.3"
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# `@img/sharp-libvips-linuxmusl-x64`
|
||||
|
||||
Prebuilt libvips and dependencies for use with sharp on Linux (musl) x64.
|
||||
|
||||
## Licensing
|
||||
|
||||
This software contains third-party libraries
|
||||
used under the terms of the following licenses:
|
||||
|
||||
| Library | Used under the terms of |
|
||||
|---------------|-----------------------------------------------------------------------------------------------------------|
|
||||
| aom | BSD 2-Clause + [Alliance for Open Media Patent License 1.0](https://aomedia.org/license/patent-license/) |
|
||||
| cairo | Mozilla Public License 1.1 |
|
||||
| cgif | MIT License |
|
||||
| expat | MIT License |
|
||||
| fontconfig | [fontconfig License](https://gitlab.freedesktop.org/fontconfig/fontconfig/blob/main/COPYING) (BSD-like) |
|
||||
| freetype | [freetype License](https://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) |
|
||||
| fribidi | LGPLv3 |
|
||||
| glib | LGPLv3 |
|
||||
| harfbuzz | MIT License |
|
||||
| highway | BSD 3-Clause |
|
||||
| lcms | MIT License |
|
||||
| libarchive | BSD 2-Clause |
|
||||
| libexif | LGPLv3 |
|
||||
| libffi | MIT License |
|
||||
| libheif | LGPLv3 |
|
||||
| libimagequant | [BSD 2-Clause](https://github.com/lovell/libimagequant/blob/main/COPYRIGHT) |
|
||||
| libnsgif | MIT License |
|
||||
| libpng | [libpng License](https://github.com/pnggroup/libpng/blob/master/LICENSE) |
|
||||
| librsvg | LGPLv3 |
|
||||
| libtiff | [libtiff License](https://gitlab.com/libtiff/libtiff/blob/master/LICENSE.md) (BSD-like) |
|
||||
| libultrahdr | MIT License |
|
||||
| libvips | LGPLv3 |
|
||||
| libwebp | New BSD License |
|
||||
| libxml2 | MIT License |
|
||||
| mozjpeg | [zlib License, IJG License, BSD 3-Clause](https://github.com/mozilla/mozjpeg/blob/master/LICENSE.md) |
|
||||
| pango | LGPLv3 |
|
||||
| pixman | MIT License |
|
||||
| proxy-libintl | LGPLv3 |
|
||||
| zlib-ng | [zlib License](https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md) |
|
||||
|
||||
Use of libraries under the terms of the LGPLv3 is via the
|
||||
"any later version" clause of the LGPLv2 or LGPLv2.1.
|
||||
|
||||
Please report any errors or omissions via
|
||||
https://github.com/lovell/sharp-libvips/issues/new
|
||||
Generated
Vendored
+219
@@ -0,0 +1,219 @@
|
||||
/* glibconfig.h
|
||||
*
|
||||
* This is a generated file. Please modify 'glibconfig.h.in'
|
||||
*/
|
||||
|
||||
#ifndef __GLIBCONFIG_H__
|
||||
#define __GLIBCONFIG_H__
|
||||
|
||||
#include <glib/gmacros.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <float.h>
|
||||
#define GLIB_HAVE_ALLOCA_H
|
||||
|
||||
#define GLIB_STATIC_COMPILATION 1
|
||||
#define GOBJECT_STATIC_COMPILATION 1
|
||||
#define GIO_STATIC_COMPILATION 1
|
||||
#define GMODULE_STATIC_COMPILATION 1
|
||||
#define GI_STATIC_COMPILATION 1
|
||||
|
||||
/* Specifies that GLib's g_print*() functions wrap the
|
||||
* system printf functions. This is useful to know, for example,
|
||||
* when using glibc's register_printf_function().
|
||||
*/
|
||||
#define GLIB_USING_SYSTEM_PRINTF
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define G_MINFLOAT FLT_MIN
|
||||
#define G_MAXFLOAT FLT_MAX
|
||||
#define G_MINDOUBLE DBL_MIN
|
||||
#define G_MAXDOUBLE DBL_MAX
|
||||
#define G_MINSHORT SHRT_MIN
|
||||
#define G_MAXSHORT SHRT_MAX
|
||||
#define G_MAXUSHORT USHRT_MAX
|
||||
#define G_MININT INT_MIN
|
||||
#define G_MAXINT INT_MAX
|
||||
#define G_MAXUINT UINT_MAX
|
||||
#define G_MINLONG LONG_MIN
|
||||
#define G_MAXLONG LONG_MAX
|
||||
#define G_MAXULONG ULONG_MAX
|
||||
|
||||
typedef signed char gint8;
|
||||
typedef unsigned char guint8;
|
||||
|
||||
typedef signed short gint16;
|
||||
typedef unsigned short guint16;
|
||||
|
||||
#define G_GINT16_MODIFIER "h"
|
||||
#define G_GINT16_FORMAT "hi"
|
||||
#define G_GUINT16_FORMAT "hu"
|
||||
|
||||
|
||||
typedef signed int gint32;
|
||||
typedef unsigned int guint32;
|
||||
|
||||
#define G_GINT32_MODIFIER ""
|
||||
#define G_GINT32_FORMAT "i"
|
||||
#define G_GUINT32_FORMAT "u"
|
||||
|
||||
|
||||
#define G_HAVE_GINT64 1 /* deprecated, always true */
|
||||
|
||||
typedef signed long gint64;
|
||||
typedef unsigned long guint64;
|
||||
|
||||
#define G_GINT64_CONSTANT(val) (val##L)
|
||||
#define G_GUINT64_CONSTANT(val) (val##UL)
|
||||
|
||||
#define G_GINT64_MODIFIER "l"
|
||||
#define G_GINT64_FORMAT "li"
|
||||
#define G_GUINT64_FORMAT "lu"
|
||||
|
||||
|
||||
#define GLIB_SIZEOF_VOID_P 8
|
||||
#define GLIB_SIZEOF_LONG 8
|
||||
#define GLIB_SIZEOF_SIZE_T 8
|
||||
#define GLIB_SIZEOF_SSIZE_T 8
|
||||
|
||||
typedef signed long gssize;
|
||||
typedef unsigned long gsize;
|
||||
#define G_GSIZE_MODIFIER "l"
|
||||
#define G_GSSIZE_MODIFIER "l"
|
||||
#define G_GSIZE_FORMAT "lu"
|
||||
#define G_GSSIZE_FORMAT "li"
|
||||
|
||||
#define G_MAXSIZE G_MAXULONG
|
||||
#define G_MINSSIZE G_MINLONG
|
||||
#define G_MAXSSIZE G_MAXLONG
|
||||
|
||||
typedef gint64 goffset;
|
||||
#define G_MINOFFSET G_MININT64
|
||||
#define G_MAXOFFSET G_MAXINT64
|
||||
|
||||
#define G_GOFFSET_MODIFIER G_GINT64_MODIFIER
|
||||
#define G_GOFFSET_FORMAT G_GINT64_FORMAT
|
||||
#define G_GOFFSET_CONSTANT(val) G_GINT64_CONSTANT(val)
|
||||
|
||||
#define G_POLLFD_FORMAT "%d"
|
||||
|
||||
#define GPOINTER_TO_INT(p) ((gint) (glong) (p))
|
||||
#define GPOINTER_TO_UINT(p) ((guint) (gulong) (p))
|
||||
|
||||
#define GINT_TO_POINTER(i) ((gpointer) (glong) (i))
|
||||
#define GUINT_TO_POINTER(u) ((gpointer) (gulong) (u))
|
||||
|
||||
typedef signed long gintptr;
|
||||
typedef unsigned long guintptr;
|
||||
|
||||
#define G_GINTPTR_MODIFIER "l"
|
||||
#define G_GINTPTR_FORMAT "li"
|
||||
#define G_GUINTPTR_FORMAT "lu"
|
||||
|
||||
#define GLIB_MAJOR_VERSION 2
|
||||
#define GLIB_MINOR_VERSION 90
|
||||
#define GLIB_MICRO_VERSION 0
|
||||
|
||||
#define G_OS_UNIX
|
||||
|
||||
#define G_VA_COPY va_copy
|
||||
|
||||
#define G_VA_COPY_AS_ARRAY 1
|
||||
|
||||
#define G_HAVE_ISO_VARARGS 1
|
||||
|
||||
/* gcc-2.95.x supports both gnu style and ISO varargs, but if -ansi
|
||||
* is passed ISO vararg support is turned off, and there is no work
|
||||
* around to turn it on, so we unconditionally turn it off.
|
||||
*/
|
||||
#if __GNUC__ == 2 && __GNUC_MINOR__ == 95
|
||||
# undef G_HAVE_ISO_VARARGS
|
||||
#endif
|
||||
|
||||
#define G_HAVE_GROWING_STACK 0
|
||||
|
||||
#ifndef _MSC_VER
|
||||
# define G_HAVE_GNUC_VARARGS 1
|
||||
#endif
|
||||
|
||||
#if defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)
|
||||
#define G_GNUC_INTERNAL __attribute__((visibility("hidden")))
|
||||
#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x550)
|
||||
#define G_GNUC_INTERNAL __hidden
|
||||
#elif defined (__GNUC__) && defined (G_HAVE_GNUC_VISIBILITY)
|
||||
#define G_GNUC_INTERNAL __attribute__((visibility("hidden")))
|
||||
#else
|
||||
#define G_GNUC_INTERNAL
|
||||
#endif
|
||||
|
||||
#define G_THREADS_ENABLED
|
||||
#define G_THREADS_IMPL_POSIX
|
||||
|
||||
#define G_ATOMIC_LOCK_FREE
|
||||
|
||||
#define GINT16_TO_LE(val) ((gint16) (val))
|
||||
#define GUINT16_TO_LE(val) ((guint16) (val))
|
||||
#define GINT16_TO_BE(val) ((gint16) GUINT16_SWAP_LE_BE (val))
|
||||
#define GUINT16_TO_BE(val) (GUINT16_SWAP_LE_BE (val))
|
||||
|
||||
#define GINT32_TO_LE(val) ((gint32) (val))
|
||||
#define GUINT32_TO_LE(val) ((guint32) (val))
|
||||
#define GINT32_TO_BE(val) ((gint32) GUINT32_SWAP_LE_BE (val))
|
||||
#define GUINT32_TO_BE(val) (GUINT32_SWAP_LE_BE (val))
|
||||
|
||||
#define GINT64_TO_LE(val) ((gint64) (val))
|
||||
#define GUINT64_TO_LE(val) ((guint64) (val))
|
||||
#define GINT64_TO_BE(val) ((gint64) GUINT64_SWAP_LE_BE (val))
|
||||
#define GUINT64_TO_BE(val) (GUINT64_SWAP_LE_BE (val))
|
||||
|
||||
#define GLONG_TO_LE(val) ((glong) GINT64_TO_LE (val))
|
||||
#define GULONG_TO_LE(val) ((gulong) GUINT64_TO_LE (val))
|
||||
#define GLONG_TO_BE(val) ((glong) GINT64_TO_BE (val))
|
||||
#define GULONG_TO_BE(val) ((gulong) GUINT64_TO_BE (val))
|
||||
#define GINT_TO_LE(val) ((gint) GINT32_TO_LE (val))
|
||||
#define GUINT_TO_LE(val) ((guint) GUINT32_TO_LE (val))
|
||||
#define GINT_TO_BE(val) ((gint) GINT32_TO_BE (val))
|
||||
#define GUINT_TO_BE(val) ((guint) GUINT32_TO_BE (val))
|
||||
#define GSIZE_TO_LE(val) ((gsize) GUINT64_TO_LE (val))
|
||||
#define GSSIZE_TO_LE(val) ((gssize) GINT64_TO_LE (val))
|
||||
#define GSIZE_TO_BE(val) ((gsize) GUINT64_TO_BE (val))
|
||||
#define GSSIZE_TO_BE(val) ((gssize) GINT64_TO_BE (val))
|
||||
#define G_BYTE_ORDER G_LITTLE_ENDIAN
|
||||
|
||||
#define GLIB_SYSDEF_POLLIN =1
|
||||
#define GLIB_SYSDEF_POLLOUT =4
|
||||
#define GLIB_SYSDEF_POLLPRI =2
|
||||
#define GLIB_SYSDEF_POLLHUP =16
|
||||
#define GLIB_SYSDEF_POLLERR =8
|
||||
#define GLIB_SYSDEF_POLLNVAL =32
|
||||
|
||||
/* No way to disable deprecation warnings for macros, so only emit deprecation
|
||||
* warnings on platforms where usage of this macro is broken */
|
||||
#if defined(__APPLE__) || defined(_MSC_VER) || defined(__CYGWIN__)
|
||||
#define G_MODULE_SUFFIX "so" GLIB_DEPRECATED_MACRO_IN_2_76
|
||||
#else
|
||||
#define G_MODULE_SUFFIX "so"
|
||||
#endif
|
||||
|
||||
typedef int GPid;
|
||||
#define G_PID_FORMAT "i"
|
||||
|
||||
#define GLIB_SYSDEF_AF_UNIX 1
|
||||
#define GLIB_SYSDEF_AF_INET 2
|
||||
#define GLIB_SYSDEF_AF_INET6 10
|
||||
|
||||
#define GLIB_SYSDEF_MSG_OOB 1
|
||||
#define GLIB_SYSDEF_MSG_PEEK 2
|
||||
#define GLIB_SYSDEF_MSG_DONTROUTE 4
|
||||
|
||||
#define G_DIR_SEPARATOR '/'
|
||||
#define G_DIR_SEPARATOR_S "/"
|
||||
#define G_SEARCHPATH_SEPARATOR ':'
|
||||
#define G_SEARCHPATH_SEPARATOR_S ":"
|
||||
|
||||
#undef G_HAVE_FREE_SIZED
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GLIBCONFIG_H__ */
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = __dirname;
|
||||
Generated
Vendored
BIN
Binary file not shown.
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@img/sharp-libvips-linuxmusl-x64",
|
||||
"version": "1.3.4",
|
||||
"description": "Prebuilt libvips and dependencies for use with sharp on Linux (musl) x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lovell/sharp-libvips.git",
|
||||
"directory": "npm/linuxmusl-x64"
|
||||
},
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"versions.json"
|
||||
],
|
||||
"type": "commonjs",
|
||||
"exports": {
|
||||
"./lib": "./lib/index.js",
|
||||
"./package": "./package.json",
|
||||
"./versions": "./versions.json",
|
||||
"./binary": "./lib/libvips-cpp.so.8.18.7"
|
||||
},
|
||||
"config": {
|
||||
"musl": ">=1.2.5"
|
||||
},
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"aom": "3.15.1",
|
||||
"archive": "3.8.9",
|
||||
"cairo": "1.18.6",
|
||||
"cgif": "0.5.4",
|
||||
"exif": "0.6.26",
|
||||
"expat": "2.8.5",
|
||||
"ffi": "3.8.0",
|
||||
"fontconfig": "2.18.3",
|
||||
"freetype": "2.14.3",
|
||||
"fribidi": "1.0.17",
|
||||
"glib": "2.90.0",
|
||||
"harfbuzz": "14.5.0",
|
||||
"heif": "1.23.5",
|
||||
"highway": "1.4.0",
|
||||
"imagequant": "2.4.1",
|
||||
"lcms": "2.19.1",
|
||||
"mozjpeg": "0826579",
|
||||
"pango": "1.58.2",
|
||||
"pixman": "0.46.4",
|
||||
"png": "1.6.58",
|
||||
"proxy-libintl": "0.5",
|
||||
"rsvg": "2.63.2",
|
||||
"tiff": "4.7.2",
|
||||
"uhdr": "2.0.2",
|
||||
"vips": "8.18.7",
|
||||
"webp": "1.6.0",
|
||||
"xml2": "2.15.4",
|
||||
"zlib-ng": "2.3.3"
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# `@img/sharp-linuxmusl-x64`
|
||||
|
||||
Prebuilt sharp for use with Linux (musl) x64.
|
||||
|
||||
## Licensing
|
||||
|
||||
Copyright 2013 Lovell Fuller and others.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
try { require.resolve('@img/sharp-libvips-linuxmusl-x64/binary'); } catch {}
|
||||
module.exports = require('./lib/sharp-linuxmusl-x64-0.35.5.node');
|
||||
Generated
Vendored
BIN
Binary file not shown.
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@img/sharp-linuxmusl-x64",
|
||||
"version": "0.35.5",
|
||||
"description": "Prebuilt sharp for use with Linux (musl) x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lovell/sharp.git",
|
||||
"directory": "npm/linuxmusl-x64"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.4"
|
||||
},
|
||||
"files": [
|
||||
"index.cjs",
|
||||
"lib"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "commonjs",
|
||||
"exports": {
|
||||
"./sharp.node": "./index.cjs",
|
||||
"./package": "./package.json"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"config": {
|
||||
"musl": ">=1.2.5"
|
||||
},
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# `@node-rs/argon2-linux-x64-musl`
|
||||
|
||||
This is the **x86_64-unknown-linux-musl** binary for `@node-rs/argon2`
|
||||
Generated
Vendored
BIN
Binary file not shown.
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@node-rs/argon2-linux-x64-musl",
|
||||
"version": "2.2.2",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"main": "argon2.linux-x64-musl.node",
|
||||
"files": [
|
||||
"argon2.linux-x64-musl.node"
|
||||
],
|
||||
"description": "argon2-rust binding for Node.js",
|
||||
"keywords": [
|
||||
"argon2",
|
||||
"argon2-password",
|
||||
"crypto",
|
||||
"encryption",
|
||||
"hash",
|
||||
"hashing",
|
||||
"password",
|
||||
"secure",
|
||||
"verify"
|
||||
],
|
||||
"homepage": "https://github.com/napi-rs/node-rs",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/napi-rs/node-rs.git"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"access": "public"
|
||||
},
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-present LongYinan
|
||||
|
||||
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.
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
# `@node-rs/argon2`
|
||||
|
||||

|
||||

|
||||
|
||||
[argon2-rust](https://crates.io/crates/argon2-rust) binding for Node.js.
|
||||
|
||||
Argon2 is a [key derivation function](https://en.wikipedia.org/wiki/Key_derivation_function) that was selected as the winner of the [Password Hashing Competition(PHC)](https://password-hashing.net) in July 2015.
|
||||
|
||||
Argon2 summarizes the state of the art in the design of memory-hard functions and can be used to hash passwords for credential storage, key derivation, or other applications.
|
||||
|
||||
It has a simple design aimed at the highest memory filling rate and effective use of multiple computing units, while still providing defense against tradeoff attacks (by exploiting the cache and memory organization of the recent processors).
|
||||
|
||||
## Features
|
||||
|
||||
- Faster than node-argon2 and `node:crypto.argon2` at the same m/t/p (see Benchmarks).
|
||||
- No node-gyp and postinstall.
|
||||
- Cross-platform support, including [Apple M1](https://www.apple.com/newsroom/2020/11/apple-unleashes-m1/).
|
||||
- Smaller file size after npm installation(476K vs [node-argon2](https://github.com/ranisalt/node-argon2) 3.7M).
|
||||
- `@node-rs/argon2` supports all three algorithms:
|
||||
- Argon2i: Optimizes against GPU cracking attacks but vulnerable to side-channels.
|
||||
Accesses the memory array in a password dependent order, reducing the possibility of time–memory tradeoff (TMTO) attacks.
|
||||
- Argon2d: Optimized to resist side-channel attacks.
|
||||
Accesses the memory array in a password independent order, increasing the possibility of time-memory tradeoff (TMTO) attacks.
|
||||
- **Argon2id**: default value, this is the default algorithm for normative recommendations.
|
||||
Hybrid that mixes Argon2i and Argon2d passes.
|
||||
Uses the Argon2i approach for the first half pass over memory and Argon2d approach for subsequent passes. This effectively places it in the “middle” between the other two: it doesn’t provide as good TMTO/GPU cracking resistance as Argon2d, nor as good of side-channel resistance as Argon2i, but overall provides the most well-rounded approach to both classes of attacks.
|
||||
|
||||
# Benchmarks
|
||||
|
||||
Comparing each library's **defaults** is not 1:1. `@node-rs/argon2` defaults to `m=19456,t=2,p=1`; `argon2` (node-argon2) defaults to `m=65536,t=3,p=4`. See [#841](https://github.com/napi-rs/node-rs/issues/841).
|
||||
|
||||
This bench pins the same password, salt, Argon2id v=19, m, t, p, and 32-byte tag. Only the **raw** KDF is timed. The tag is asserted equal on every run. Native impls are interleaved with each other; JS/wasm are a separate group so a 7 ms hash is not timed after a 400 ms JS loop.
|
||||
|
||||
Apple M5 Max / arm64 / Node 24 / [argon2-rust 1.1.0](https://crates.io/crates/argon2-rust). Median ms. See [benchmark/](benchmark/argon2.ts).
|
||||
|
||||
**Native async raw** — same calling shape as node-argon2 (no sync API there):
|
||||
|
||||
| params | @node-rs/argon2 | node-argon2 | node:crypto |
|
||||
| --------------------- | --------------: | ----------: | ----------: |
|
||||
| m=19456 KiB, t=2, p=1 | **7.58** | 15.41 | 13.05 |
|
||||
| m=65536 KiB, t=3, p=1 | **49.35** | 84.57 | 71.71 |
|
||||
| m=65536 KiB, t=3, p=4 | **13.62** | 22.54 | 20.83 |
|
||||
|
||||
**JS / wasm raw** — same params, same tags:
|
||||
|
||||
| params | hash-wasm | @noble/hashes |
|
||||
| --------------------- | --------: | ------------: |
|
||||
| m=19456 KiB, t=2, p=1 | 19.79 | 86.14 |
|
||||
| m=65536 KiB, t=3, p=1 | 104.09 | 433.07 |
|
||||
| m=65536 KiB, t=3, p=4 | 106.06 | 435.39 |
|
||||
|
||||
hash-wasm and `@noble/hashes` do not run lanes in parallel, so their p=4 row is the p=1 work.
|
||||
|
||||
## API
|
||||
|
||||
```typescript
|
||||
export const enum Algorithm {
|
||||
Argon2d = 0,
|
||||
Argon2i = 1,
|
||||
Argon2id = 2,
|
||||
}
|
||||
export const enum Version {
|
||||
/** Version 16 (0x10 in hex) */
|
||||
V0x10 = 0,
|
||||
/**
|
||||
* Default value
|
||||
* Version 19 (0x13 in hex, default)
|
||||
*/
|
||||
V0x13 = 1,
|
||||
}
|
||||
export interface Options {
|
||||
/**
|
||||
* The amount of memory to be used by the hash function, in kilobytes. Each thread will have a memory pool of this size. Note that large values for highly concurrent usage will cause starvation and thrashing if your system memory gets full.
|
||||
*
|
||||
* Value is an integer in decimal (1 to 10 digits), between 1 and (2^32)-1.
|
||||
*
|
||||
* The default value is 19456, meaning a pool of 19 MiB per thread.
|
||||
*/
|
||||
memoryCost?: number | undefined | null
|
||||
/**
|
||||
* The time cost is the amount of passes (iterations) used by the hash function. It increases hash strength at the cost of time required to compute.
|
||||
*
|
||||
* Value is an integer in decimal (1 to 10 digits), between 1 and (2^32)-1.
|
||||
*
|
||||
* The default value is 2.
|
||||
*/
|
||||
timeCost?: number | undefined | null
|
||||
/**
|
||||
* The hash length is the length of the hash function output in bytes. Note that the resulting hash is encoded with Base 64, so the digest will be ~1/3 longer.
|
||||
*
|
||||
* The default value is 32, which produces raw hashes of 32 bytes or digests of 43 characters.
|
||||
*/
|
||||
outputLen?: number | undefined | null
|
||||
/**
|
||||
* The amount of threads to compute the hash on. Each thread has a memory pool with memoryCost size. Note that changing it also changes the resulting hash.
|
||||
*
|
||||
* Value is an integer in decimal (1 to 3 digits), between 1 and 255.
|
||||
*
|
||||
* The default value is 1, meaning a single thread is used.
|
||||
*/
|
||||
parallelism?: number | undefined | null
|
||||
algorithm?: Algorithm | undefined | null
|
||||
version?: Version | undefined | null
|
||||
secret?: Buffer | undefined | null
|
||||
}
|
||||
export function hash(
|
||||
password: string | Buffer,
|
||||
options?: Options | undefined | null,
|
||||
abortSignal?: AbortSignal | undefined | null,
|
||||
): Promise<string>
|
||||
export function verify(
|
||||
hashed: string | Buffer,
|
||||
password: string | Buffer,
|
||||
options?: Options | undefined | null,
|
||||
abortSignal?: AbortSignal | undefined | null,
|
||||
): Promise<boolean>
|
||||
```
|
||||
|
||||
## `parseOptions` — needs-rehash checks
|
||||
|
||||
`parseOptions` parses an encoded PHC hash string and returns the parameters it was created with. Compare them against your current policy to decide whether a stored hash should be rehashed at next login.
|
||||
|
||||
```typescript
|
||||
import { Algorithm, parseOptions, Version } from '@node-rs/argon2'
|
||||
|
||||
const POLICY = {
|
||||
algorithm: Algorithm.Argon2id,
|
||||
version: Version.V0x13,
|
||||
memoryCost: 19456,
|
||||
timeCost: 2,
|
||||
parallelism: 1,
|
||||
outputLen: 32,
|
||||
} as const
|
||||
|
||||
function needsRehash(hashed: string): boolean {
|
||||
const parsed = parseOptions(hashed)
|
||||
return (
|
||||
parsed.algorithm !== POLICY.algorithm ||
|
||||
parsed.version !== POLICY.version ||
|
||||
parsed.memoryCost !== POLICY.memoryCost ||
|
||||
parsed.timeCost !== POLICY.timeCost ||
|
||||
parsed.parallelism !== POLICY.parallelism ||
|
||||
parsed.outputLen !== POLICY.outputLen ||
|
||||
parsed.saltLen < 16
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
export interface ParsedHashOptions {
|
||||
algorithm: Algorithm
|
||||
version: Version
|
||||
/** Memory cost in kibibytes (`m=` in the PHC string). */
|
||||
memoryCost: number
|
||||
/** Time cost / number of passes (`t=` in the PHC string). */
|
||||
timeCost: number
|
||||
/** Degree of parallelism (`p=` in the PHC string). */
|
||||
parallelism: number
|
||||
/** Length of the raw hash output in bytes. */
|
||||
outputLen: number
|
||||
/** Byte length of the decoded salt. This package generates 16-byte salts; older hashes may carry shorter ones. */
|
||||
saltLen: number
|
||||
}
|
||||
export function parseOptions(hashed: string | Uint8Array): ParsedHashOptions
|
||||
```
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from '@node-rs/argon2-wasm32-wasi'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user