First commit
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# مستندسازی معماری پروژه Ai-School Frontend
|
||||
|
||||
## بررسی و مستندسازی معماری کامل پروژه
|
||||
|
||||
این پروژه یک ادمین پنل Next.js 15 با معماری پیشرفته است که بر پایه Vuexy Template ساخته شده است.
|
||||
|
||||
### ساختار کلی پروژه
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── @core/ # کامپوننتها و ابزارهای اصلی
|
||||
│ ├── @layouts/ # لایهبندیهای مختلف (Vertical/Horizontal)
|
||||
│ ├── @menu/ # سیستم منو (عمودی/افقی)
|
||||
│ ├── app/ # Next.js App Router
|
||||
│ ├── components/ # کامپوننتهای قابل استفاده مجدد
|
||||
│ ├── contexts/ # Context API برای state management
|
||||
│ ├── libs/ # کتابخانههای شخصیسازی شده
|
||||
│ ├── redux-store/ # Redux store و slices
|
||||
│ ├── views/ # View components برای صفحات
|
||||
│ └── utils/ # توابع کمکی
|
||||
```
|
||||
|
||||
### 1. ساختار Routing (Next.js App Router)
|
||||
|
||||
#### مسیرهای اصلی:
|
||||
|
||||
- `/layout.tsx` - Root layout
|
||||
- `/(dashboard)/(private)/` - صفحات احراز هویت شده
|
||||
- `/(blank-layout-pages)/(guest-only)/` - صفحات مهمان (Login, Register)
|
||||
- `/(blank-layout-pages)/pages/` - صفحات عمومی
|
||||
|
||||
#### نحوه Fetch کردن Routes:
|
||||
|
||||
- Routes به صورت فایلهای `page.tsx` در دایرکتوریهای `app/(dashboard)/(private)/` تعریف میشوند
|
||||
- هر صفحه میتواند Server Component باشد و دادهها را از API fetch کند
|
||||
- مثال: `src/app/(dashboard)/(private)/dashboards/crm/page.tsx`
|
||||
|
||||
#### Redirects:
|
||||
|
||||
- در `next.config.ts` تعریف شدهاند
|
||||
- `/` به `/dashboards/crm` redirect میشود
|
||||
|
||||
### 2. کامپوننتها
|
||||
|
||||
#### کامپوننتهای آماده (Core Components):
|
||||
|
||||
- **@core/components/mui/** - کامپوننتهای MUI شخصیسازی شده:
|
||||
- `Autocomplete.tsx`
|
||||
- `Avatar.tsx`
|
||||
- `Badge.tsx`
|
||||
- `Chip.tsx`
|
||||
- `IconButton.tsx`
|
||||
- `TabList.tsx`
|
||||
- `TextField.tsx`
|
||||
|
||||
- **@core/components/custom-inputs/** - Input های سفارشی:
|
||||
- `Horizontal.tsx` - Input با label افقی
|
||||
- `Vertical.tsx` - Input با label عمودی
|
||||
- `Image.tsx` - Input برای آپلود تصویر
|
||||
|
||||
- **@core/components/customizer/** - تنظیمات تم و layout
|
||||
- **@core/components/scroll-to-top/** - دکمه بازگشت به بالا
|
||||
- **@core/components/option-menu/** - منوی انتخاب
|
||||
|
||||
#### کامپوننتهای Layout:
|
||||
|
||||
- **components/layout/vertical/** - کامپوننتهای layout عمودی:
|
||||
- `Navigation.tsx` - منوی کناری
|
||||
- `Navbar.tsx` - نوار بالایی
|
||||
- `Footer.tsx` - فوتر
|
||||
|
||||
- **components/layout/horizontal/** - کامپوننتهای layout افقی:
|
||||
- `Header.tsx` - هدر افقی
|
||||
- `Navigation.tsx` - منوی افقی
|
||||
- `Footer.tsx` - فوتر
|
||||
|
||||
- **components/layout/shared/** - کامپوننتهای مشترک:
|
||||
- `UserDropdown.tsx` - منوی کاربر
|
||||
- `NotificationsDropdown.tsx` - اعلانها
|
||||
- `ModeDropdown.tsx` - تغییر تم (Dark/Light)
|
||||
- `search/` - جستجو
|
||||
|
||||
#### کامپوننتهای آماده استفاده:
|
||||
|
||||
- **components/card-statistics/** - کارتهای آمار:
|
||||
- `Vertical.tsx` - کارت عمودی
|
||||
- `Horizontal.tsx` - کارت افقی
|
||||
- `StatsWithAreaChart.tsx` - کارت با نمودار
|
||||
|
||||
- **components/dialogs/** - دیالوگهای آماده:
|
||||
- `confirmation-dialog/` - تایید عملیات
|
||||
- `edit-user-info/` - ویرایش اطلاعات کاربر
|
||||
- `role-dialog/` - مدیریت نقشها
|
||||
- `permission-dialog/` - مدیریت دسترسیها
|
||||
|
||||
- **components/pricing/** - کامپوننتهای قیمتگذاری
|
||||
- **components/stepper-dot/** - Stepper با نقطه
|
||||
|
||||
### 3. سیستم منو (Menu System)
|
||||
|
||||
#### ساختار منو:
|
||||
|
||||
- **@menu/vertical-menu/** - منوی عمودی:
|
||||
- `Menu.tsx` - منوی اصلی
|
||||
- `MenuItem.tsx` - آیتم منو
|
||||
- `SubMenu.tsx` - زیرمنو
|
||||
- `MenuSection.tsx` - بخش منو
|
||||
|
||||
- **@menu/horizontal-menu/** - منوی افقی:
|
||||
- `Menu.tsx`
|
||||
- `MenuItem.tsx`
|
||||
- `SubMenu.tsx`
|
||||
|
||||
#### دادههای منو:
|
||||
|
||||
- **src/data/navigation/verticalMenuData.tsx** - دادههای منوی عمودی
|
||||
- **src/components/GenerateMenu.tsx** - تابع تولید منو از دادهها
|
||||
|
||||
#### Contexts:
|
||||
|
||||
- `@menu/contexts/verticalNavContext.tsx` - Context منوی عمودی
|
||||
- `@menu/contexts/horizontalNavContext.tsx` - Context منوی افقی
|
||||
|
||||
#### Hooks:
|
||||
|
||||
- `@menu/hooks/useVerticalMenu.tsx` - Hook منوی عمودی
|
||||
- `@menu/hooks/useHorizontalMenu.tsx` - Hook منوی افقی
|
||||
- `@menu/hooks/useVerticalNav.tsx` - Hook navigation عمودی
|
||||
- `@menu/hooks/useHorizontalNav.tsx` - Hook navigation افقی
|
||||
|
||||
### 4. ساختار API
|
||||
|
||||
#### API Client:
|
||||
|
||||
- **src/libs/api/client.ts** - کلاس `ApiClient`:
|
||||
- مدیریت base URL
|
||||
- مدیریت authentication token (از localStorage)
|
||||
- متدهای `get`, `post`, `put`, `delete`
|
||||
- مدیریت خطاها و 401 (unauthorized)
|
||||
|
||||
#### API Services:
|
||||
|
||||
- **src/libs/api/services/**:
|
||||
- `authService.ts` - احراز هویت:
|
||||
- `requestOTP(phoneNumber)` - درخواست OTP
|
||||
- `verifyOTP(token, otpCode)` - تایید OTP
|
||||
- `logout()` - خروج
|
||||
- `taskService.ts` - مدیریت تسکها
|
||||
- `eventService.ts` - مدیریت رویدادها
|
||||
- `simulatorService.ts` - شبیهساز
|
||||
|
||||
#### نحوه استفاده:
|
||||
|
||||
```typescript
|
||||
import { authService } from '@/libs/api'
|
||||
|
||||
// درخواست OTP
|
||||
const tempToken = await authService.requestOTP(phoneNumber)
|
||||
|
||||
// تایید OTP
|
||||
await authService.verifyOTP(tempToken, otpCode)
|
||||
```
|
||||
|
||||
### 5. Hooks
|
||||
|
||||
#### Core Hooks (@core/hooks):
|
||||
|
||||
- `useSettings.tsx` - دسترسی به تنظیمات تم و layout
|
||||
- `useLayoutInit.ts` - مقداردهی اولیه layout
|
||||
- `useImageVariant.ts` - مدیریت variant تصاویر
|
||||
- `useObjectCookie.ts` - مدیریت cookie به صورت object
|
||||
|
||||
#### Custom Hooks (hooks):
|
||||
|
||||
- `useIntersection.ts` - Intersection Observer برای lazy loading
|
||||
|
||||
#### Menu Hooks (@menu/hooks):
|
||||
|
||||
- `useVerticalMenu.tsx` - مدیریت state منوی عمودی
|
||||
- `useHorizontalMenu.tsx` - مدیریت state منوی افقی
|
||||
- `useVerticalNav.tsx` - مدیریت navigation عمودی
|
||||
- `useHorizontalNav.tsx` - مدیریت navigation افقی
|
||||
- `useMediaQuery.tsx` - Responsive breakpoints
|
||||
|
||||
### 6. مدیریت State
|
||||
|
||||
#### Redux Store:
|
||||
|
||||
- **src/redux-store/index.ts** - تنظیمات store:
|
||||
- `chatReducer` - state چت
|
||||
- `calendarReducer` - state تقویم
|
||||
- `kanbanReducer` - state کانبان
|
||||
- `emailReducer` - state ایمیل
|
||||
|
||||
#### Context API:
|
||||
|
||||
- **src/contexts/authContext.tsx** - مدیریت احراز هویت:
|
||||
- `user` - اطلاعات کاربر
|
||||
- `isAuthenticated` - وضعیت احراز هویت
|
||||
- `isLoading` - وضعیت بارگذاری
|
||||
- `login()` - ورود
|
||||
- `logout()` - خروج
|
||||
- `requestOTP()` - درخواست OTP
|
||||
|
||||
- **@core/contexts/settingsContext.tsx** - تنظیمات تم و layout
|
||||
- **@menu/contexts/** - Contexts منو
|
||||
- **src/contexts/intersectionContext.tsx** - Intersection Observer
|
||||
|
||||
### 7. Layouts
|
||||
|
||||
#### Vertical Layout:
|
||||
|
||||
- **@layouts/VerticalLayout.tsx** - Layout عمودی:
|
||||
- Navigation (منوی کناری)
|
||||
- Navbar (نوار بالایی)
|
||||
- Content (محتوای اصلی)
|
||||
- Footer (فوتر)
|
||||
|
||||
#### Horizontal Layout:
|
||||
|
||||
- **@layouts/HorizontalLayout.tsx** - Layout افقی:
|
||||
- Header (هدر)
|
||||
- Content (محتوای اصلی)
|
||||
- Footer (فوتر)
|
||||
|
||||
#### Layout Wrapper:
|
||||
|
||||
- **@layouts/LayoutWrapper.tsx** - Wrapper برای switch بین layouts
|
||||
|
||||
### 8. احراز هویت (Authentication)
|
||||
|
||||
#### AuthGuard:
|
||||
|
||||
- **src/hocs/AuthGuard.tsx** - محافظت از صفحات:
|
||||
- بررسی `isAuthenticated`
|
||||
- Redirect به صفحه login در صورت عدم احراز هویت
|
||||
|
||||
#### GuestOnlyRoute:
|
||||
|
||||
- **src/hocs/GuestOnlyRoute.tsx** - فقط برای مهمانان:
|
||||
- Redirect به dashboard در صورت احراز هویت
|
||||
|
||||
#### AuthProvider:
|
||||
|
||||
- **src/contexts/authProvider.tsx** - Provider احراز هویت
|
||||
- **src/contexts/authContext.tsx** - Context و Hook `useAuth()`
|
||||
|
||||
### 9. زبان و جهت متن
|
||||
|
||||
#### پیکربندی:
|
||||
|
||||
- **کل پروژه به فارسی است** - تمام متون، کامنتها و مستندات به فارسی
|
||||
- جهت متن: `rtl` (راست به چپ) برای تمام صفحات
|
||||
- فونت: استفاده از فونتهای فارسی (ایران سنس و غیره)
|
||||
|
||||
### 10. سیستم تم (Theme)
|
||||
|
||||
#### پیکربندی:
|
||||
|
||||
- **src/configs/themeConfig.ts** - تنظیمات:
|
||||
- `mode`: 'system' | 'light' | 'dark'
|
||||
- `skin`: 'default' | 'bordered'
|
||||
- `layout`: 'vertical' | 'collapsed' | 'horizontal'
|
||||
- `navbar`, `contentWidth`, `footer` settings
|
||||
|
||||
#### Theme Provider:
|
||||
|
||||
- **src/components/theme/** - مدیریت تم:
|
||||
- `index.tsx` - ThemeProvider
|
||||
- `mergedTheme.ts` - ادغام تمها
|
||||
- `userTheme.ts` - تم کاربر
|
||||
|
||||
#### Customizer:
|
||||
|
||||
- **@core/components/customizer/** - تنظیمات زنده تم و layout
|
||||
|
||||
### 11. Views
|
||||
|
||||
#### ساختار:
|
||||
|
||||
- **src/views/** - کامپوننتهای view برای صفحات:
|
||||
- `dashboards/` - داشبوردها
|
||||
- `apps/` - اپلیکیشنها
|
||||
- `pages/` - صفحات
|
||||
- `forms/` - فرمها
|
||||
- `charts/` - نمودارها
|
||||
|
||||
#### الگوی استفاده:
|
||||
|
||||
```tsx
|
||||
// در page.tsx
|
||||
import DashboardView from '@views/dashboards/crm'
|
||||
|
||||
const DashboardPage = async () => {
|
||||
const data = await fetchData() // Optional
|
||||
return <DashboardView data={data} />
|
||||
}
|
||||
```
|
||||
|
||||
### 12. Providers
|
||||
|
||||
#### Providers Chain:
|
||||
|
||||
- **src/components/Providers.tsx** - زنجیره Providers:
|
||||
|
||||
1. `AuthProvider` - احراز هویت
|
||||
2. `VerticalNavProvider` - منوی عمودی
|
||||
3. `SettingsProvider` - تنظیمات
|
||||
4. `ThemeProvider` - تم
|
||||
5. `ReduxProvider` - Redux
|
||||
|
||||
### 13. Path Aliases
|
||||
|
||||
#### تعریف شده در tsconfig.json:
|
||||
|
||||
- `@/*` → `./src/*`
|
||||
- `@core/*` → `./src/@core/*`
|
||||
- `@layouts/*` → `./src/@layouts/*`
|
||||
- `@menu/*` → `./src/@menu/*`
|
||||
- `@assets/*` → `./src/assets/*`
|
||||
- `@components/*` → `./src/components/*`
|
||||
- `@configs/*` → `./src/configs/*`
|
||||
- `@views/*` → `./src/views/*`
|
||||
|
||||
### 14. قوانین مهم معماری
|
||||
|
||||
1. **زبان پروژه**: **کل پروژه باید به فارسی باشد** - تمام کامنتها، متون، نام متغیرها (در صورت امکان)، و مستندات باید به فارسی نوشته شوند. پروژه تکزبانه است و فقط فارسی پشتیبانی میشود.
|
||||
2. **Routing**: همه routes در `app/` بدون `[lang]` تعریف میشوند. مسیرها مستقیماً در `app/(dashboard)/(private)/` یا `app/(blank-layout-pages)/` قرار دارند.
|
||||
3. **Components**:
|
||||
- کامپوننتهای قابل استفاده مجدد در `components/`
|
||||
- View components در `views/`
|
||||
- Core components در `@core/components/`
|
||||
4. **API**: همه API calls از طریق `libs/api/` و services
|
||||
5. **State**:
|
||||
- State محلی با Context API
|
||||
- State پیچیده با Redux (chat, calendar, kanban, email)
|
||||
6. **Authentication**: استفاده از `AuthGuard` برای صفحات protected
|
||||
7. **Theme**: استفاده از `useSettings()` برای دسترسی به تنظیمات
|
||||
8. **Layout**: استفاده از `LayoutWrapper` برای switch بین layouts
|
||||
9. **جهت متن**: تمام صفحات با جهت `rtl` (راست به چپ) هستند
|
||||
|
||||
### 15. فایلهای مهم
|
||||
|
||||
- `next.config.ts` - تنظیمات Next.js و redirects
|
||||
- `tailwind.config.ts` - تنظیمات Tailwind CSS
|
||||
- `tsconfig.json` - تنظیمات TypeScript و path aliases
|
||||
- `src/configs/themeConfig.ts` - تنظیمات تم
|
||||
- `src/data/navigation/verticalMenuData.tsx` - دادههای منو
|
||||
|
||||
---
|
||||
|
||||
**نکته مهم**: این معماری باید در تمام توسعههای آینده رعایت شود. هر تغییری باید با این ساختار سازگار باشد.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
|
||||
# Next.js
|
||||
.next
|
||||
out
|
||||
build
|
||||
|
||||
# Production
|
||||
dist
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# Debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
# Vercel
|
||||
.vercel
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose*.yml
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
File diff suppressed because it is too large
Load Diff
+77
@@ -0,0 +1,77 @@
|
||||
# Stage 1: Dependencies
|
||||
FROM node:20-alpine AS deps
|
||||
# Install OpenSSL for Prisma
|
||||
RUN apk add --no-cache openssl libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package.json ./
|
||||
|
||||
# Install dependencies (skip scripts to avoid postinstall which needs source code)
|
||||
RUN npm install --ignore-scripts
|
||||
|
||||
# Stage 2: Builder
|
||||
FROM node:20-alpine AS builder
|
||||
# Install OpenSSL for Prisma
|
||||
RUN apk add --no-cache openssl libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependencies from deps stage
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=deps /app/package.json ./package.json
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Set environment variables for build
|
||||
ARG NODE_ENV=production
|
||||
ARG NEXT_PUBLIC_APP_URL
|
||||
ARG NEXT_PUBLIC_DOCS_URL
|
||||
ARG API_URL
|
||||
ARG BASEPATH
|
||||
ARG MAPBOX_ACCESS_TOKEN
|
||||
ARG DATABASE_URL
|
||||
|
||||
ENV NODE_ENV=$NODE_ENV
|
||||
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
|
||||
ENV NEXT_PUBLIC_DOCS_URL=$NEXT_PUBLIC_DOCS_URL
|
||||
ENV API_URL=$API_URL
|
||||
ENV BASEPATH=$BASEPATH
|
||||
ENV MAPBOX_ACCESS_TOKEN=$MAPBOX_ACCESS_TOKEN
|
||||
ENV DATABASE_URL=$DATABASE_URL
|
||||
|
||||
# Generate Prisma Client and build icons (postinstall script)
|
||||
# These commands need the source code to be present
|
||||
RUN npm run build:icons
|
||||
|
||||
# Build the application
|
||||
RUN npm run build
|
||||
|
||||
# Stage 3: Runner
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=9031
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Install OpenSSL for Prisma runtime
|
||||
RUN apk add --no-cache openssl libc6-compat
|
||||
|
||||
# Create a non-root user
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy necessary files from standalone build
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 9031
|
||||
|
||||
ENV PORT=9031
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,52 @@
|
||||
# Environment Variables
|
||||
|
||||
This document describes all environment variables needed for the frontend application.
|
||||
|
||||
## Required Environment Variables
|
||||
|
||||
### Server Configuration
|
||||
- `PORT` - Server port (default: 9031)
|
||||
- `NODE_ENV` - Node environment (production/development)
|
||||
|
||||
### Next.js Configuration
|
||||
- `BASEPATH` - Base path for Next.js application (optional, leave empty for root)
|
||||
- `NEXT_PUBLIC_APP_URL` - Public URL of the application (e.g., http://localhost:9031)
|
||||
- `NEXT_PUBLIC_DOCS_URL` - Documentation URL (optional)
|
||||
|
||||
### API Configuration
|
||||
- `NEXT_PUBLIC_API_URL` or `ENVOY_GATEWAY_URL` - Envoy Gateway URL for backend API calls (e.g., http://localhost:9035)
|
||||
- This is used by the frontend to communicate with backend services via Envoy Gateway
|
||||
- Defaults to `http://localhost:9035` if not set
|
||||
|
||||
## Optional Environment Variables
|
||||
|
||||
### Mapbox
|
||||
- `MAPBOX_ACCESS_TOKEN` - Mapbox access token for map features
|
||||
|
||||
## Example .env file
|
||||
|
||||
```env
|
||||
# Server Configuration
|
||||
PORT=9031
|
||||
NODE_ENV=production
|
||||
|
||||
# Next.js Configuration
|
||||
BASEPATH=
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:9031
|
||||
NEXT_PUBLIC_DOCS_URL=https://demos.themeselection.com
|
||||
|
||||
# API Configuration (Envoy Gateway)
|
||||
NEXT_PUBLIC_API_URL=http://localhost:9035
|
||||
# Alternative: ENVOY_GATEWAY_URL=http://localhost:9035
|
||||
|
||||
# Mapbox (Optional - for map features)
|
||||
MAPBOX_ACCESS_TOKEN=your-mapbox-access-token
|
||||
```
|
||||
|
||||
## Docker Configuration
|
||||
|
||||
When using Docker, these environment variables are set in `docker-compose.yaml`.
|
||||
For local development, create a `.env` file in the frontend directory with the values above.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
@@ -0,0 +1,740 @@
|
||||
# مستندات کامل APIهای مورد نیاز Views
|
||||
|
||||
این document شامل تمام APIهایی است که توسط viewهای زیر استفاده میشوند:
|
||||
|
||||
- **Calendar** (`views/apps/calendar`)
|
||||
- **Kanban** (`views/apps/kanban`)
|
||||
- **Todo** (`views/apps/todo`)
|
||||
- **Account Settings** (`views/pages/account-settings`)
|
||||
|
||||
> **Base URL:** `NEXT_PUBLIC_API_URL` یا `ENVOY_GATEWAY_URL` یا `http://localhost:9035`
|
||||
> **Authentication:** تمام درخواستها نیاز به هدر `Authorization: Bearer {token}` دارند (توکن از `localStorage.auth_token` خوانده میشود)
|
||||
|
||||
---
|
||||
|
||||
## ۱. Calendar API (تقویم)
|
||||
|
||||
**سرویس:** `eventService`
|
||||
**Slice:** `redux-store/slices/calendar.ts`
|
||||
|
||||
### ۱.۱ لیست رویدادها
|
||||
|
||||
```
|
||||
GET /api/events
|
||||
```
|
||||
|
||||
**Query Parameters (اختیاری):**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|-----------|-------|-------------------------------------------|
|
||||
| `start` | string | ISO 8601 - تاریخ شروع فیلتر |
|
||||
| `end` | string | ISO 8601 - تاریخ پایان فیلتر |
|
||||
| `calendar`| string | فیلتر بر اساس تقویم: `Personal`, `Business`, `Family`, `Holiday`, `ETC` |
|
||||
|
||||
**مثال:**
|
||||
```
|
||||
GET /api/events?start=2025-01-01T00:00:00.000Z&end=2025-01-31T23:59:59.000Z&calendar=Business
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"id": "string",
|
||||
"title": "string",
|
||||
"description": "string",
|
||||
"deadline": 1704067200,
|
||||
"tags": ["string"],
|
||||
"author": {
|
||||
"name": "string",
|
||||
"image": "string"
|
||||
},
|
||||
"calendar": "Personal | Business | Family | Holiday | ETC",
|
||||
"start": "2025-01-15T09:00:00.000Z",
|
||||
"end": "2025-01-15T10:00:00.000Z",
|
||||
"allDay": false,
|
||||
"extendedProps": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۱.۲ ایجاد رویداد جدید
|
||||
|
||||
```
|
||||
POST /api/events
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"title": "string", // الزامی
|
||||
"description": "string", // اختیاری
|
||||
"calendar": "Personal | Business | Family | Holiday | ETC", // الزامی
|
||||
"start": "2025-01-15T09:00:00.000Z", // ISO 8601
|
||||
"end": "2025-01-15T10:00:00.000Z", // ISO 8601
|
||||
"allDay": false, // اختیاری، پیشفرض: false
|
||||
"deadline": 1704067200, // اختیاری - Unix timestamp
|
||||
"tags": ["string"], // اختیاری
|
||||
"extendedProps": {} // اختیاری
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"event": {
|
||||
"id": "string",
|
||||
"title": "string",
|
||||
"description": "string",
|
||||
"calendar": "string",
|
||||
"start": "string",
|
||||
"end": "string",
|
||||
"allDay": boolean,
|
||||
"deadline": number,
|
||||
"tags": ["string"],
|
||||
"author": {},
|
||||
"extendedProps": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۱.۳ دریافت یک رویداد
|
||||
|
||||
```
|
||||
GET /api/events/{id}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|-----------|
|
||||
| `id` | string | شناسه رویداد |
|
||||
|
||||
---
|
||||
|
||||
### ۱.۴ بهروزرسانی رویداد
|
||||
|
||||
```
|
||||
PUT /api/events/{id}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|-----------|
|
||||
| `id` | string | شناسه رویداد |
|
||||
|
||||
**Request Body (همه فیلدها اختیاری):**
|
||||
```json
|
||||
{
|
||||
"title": "string",
|
||||
"description": "string",
|
||||
"calendar": "Personal | Business | Family | Holiday | ETC",
|
||||
"start": "2025-01-15T09:00:00.000Z",
|
||||
"end": "2025-01-15T10:00:00.000Z",
|
||||
"allDay": false,
|
||||
"deadline": 1704067200,
|
||||
"tags": ["string"],
|
||||
"extendedProps": {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۱.۵ حذف رویداد
|
||||
|
||||
```
|
||||
DELETE /api/events/{id}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|-----------|
|
||||
| `id` | string | شناسه رویداد |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۲. Kanban API (کانبان)
|
||||
|
||||
**سرویس:** `kanbanService`
|
||||
**Slice:** `redux-store/slices/kanban.ts`
|
||||
|
||||
### ۲.۱ دریافت Board
|
||||
|
||||
```
|
||||
GET /api/kanban/board
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "string",
|
||||
"taskIds": [1, 2, 3]
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "string",
|
||||
"badgeText": ["string"],
|
||||
"attachments": 0,
|
||||
"comments": 0,
|
||||
"assigned": [{"src": "string", "name": "string"}],
|
||||
"image": "string",
|
||||
"dueDate": "2025-01-15T00:00:00.000Z",
|
||||
"routine": 0,
|
||||
"description": "string",
|
||||
"tags": ["string"],
|
||||
"priority": "high | medium | low",
|
||||
"status": "pending | in-progress | completed",
|
||||
"source": "kanban | calendar | todo"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **نکته:** Backend ممکن است از `badge_text` و `due_date` (snake_case) استفاده کند. سرویس frontend بهطور خودکار تبدیل میکند.
|
||||
|
||||
---
|
||||
|
||||
### ۲.۲ ایجاد ستون جدید
|
||||
|
||||
```
|
||||
POST /api/kanban/columns
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"title": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"column": {
|
||||
"id": 1,
|
||||
"title": "string",
|
||||
"taskIds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۲.۳ بهروزرسانی ستون
|
||||
|
||||
```
|
||||
PUT /api/kanban/columns/{columnId}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|------------|-------|-----------|
|
||||
| `columnId` | number | شناسه ستون |
|
||||
|
||||
**Request Body (هر دو اختیاری):**
|
||||
```json
|
||||
{
|
||||
"title": "string",
|
||||
"taskIds": [1, 2, 3]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۲.۴ حذف ستون
|
||||
|
||||
```
|
||||
DELETE /api/kanban/columns/{columnId}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|------------|-------|-----------|
|
||||
| `columnId` | number | شناسه ستون |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۲.۵ ایجاد تسک جدید
|
||||
|
||||
```
|
||||
POST /api/kanban/tasks
|
||||
```
|
||||
|
||||
**Request Body (Backend - snake_case):**
|
||||
```json
|
||||
{
|
||||
"column_id": 1, // الزامی
|
||||
"title": "string", // الزامی
|
||||
"description": "string", // اختیاری
|
||||
"badge_text": ["string"], // اختیاری
|
||||
"attachments": 0, // اختیاری
|
||||
"comments": 0, // اختیاری
|
||||
"assigned": [{"src": "string", "name": "string"}], // اختیاری
|
||||
"image": "string", // اختیاری
|
||||
"due_date": "2025-01-15T00:00:00.000Z", // اختیاری - ISO 8601
|
||||
"routine": 0, // اختیاری - 0|1|2|3|4
|
||||
"tags": ["string"], // اختیاری
|
||||
"priority": 0, // اختیاری - 0: high, 1: medium, 2: low
|
||||
"status": 0 // اختیاری - 0: pending, 1: in-progress, 2: completed
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۲.۶ بهروزرسانی تسک
|
||||
|
||||
```
|
||||
PUT /api/kanban/tasks/{taskId}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|----------|
|
||||
| `taskId`| number | شناسه تسک |
|
||||
|
||||
**Request Body (Backend - snake_case, همه اختیاری):**
|
||||
```json
|
||||
{
|
||||
"title": "string",
|
||||
"description": "string",
|
||||
"badge_text": ["string"],
|
||||
"attachments": 0,
|
||||
"comments": 0,
|
||||
"assigned": [],
|
||||
"image": "string",
|
||||
"due_date": "string",
|
||||
"routine": 0,
|
||||
"tags": ["string"],
|
||||
"priority": 0,
|
||||
"status": 0,
|
||||
"column_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۲.۷ حذف تسک
|
||||
|
||||
```
|
||||
DELETE /api/kanban/tasks/{taskId}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|----------|
|
||||
| `taskId`| number | شناسه تسک |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۲.۸ جابجایی تسک بین ستونها
|
||||
|
||||
```
|
||||
PUT /api/kanban/tasks/{taskId}/move
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|----------|
|
||||
| `taskId`| number | شناسه تسک |
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"fromColumnId": 1,
|
||||
"toColumnId": 2,
|
||||
"position": 0
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"columns": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "string",
|
||||
"taskIds": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۳. Todo API (کارهای انجامدادنی)
|
||||
|
||||
**سرویس:** `todoService`
|
||||
**استفاده مستقیم:** بدون Redux
|
||||
|
||||
### ۳.۱ لیست Todoها
|
||||
|
||||
```
|
||||
GET /api/todos
|
||||
```
|
||||
|
||||
**Query Parameters (اختیاری):**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|----------|-------|------------------------------------------------------------------|
|
||||
| `status` | string | `pending`, `in-progress`, `completed` |
|
||||
| `priority`| string | `high`, `medium`, `low` |
|
||||
| `label` | string | فیلتر بر اساس برچسب |
|
||||
| `filter` | string | `all`, `starred`, `important`, `completed`, `trashed` |
|
||||
| `search` | string | جستجو در عنوان و توضیحات |
|
||||
|
||||
**مثال:**
|
||||
```
|
||||
GET /api/todos?filter=all&search=meeting
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"todos": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "string",
|
||||
"description": "string",
|
||||
"status": "pending | in-progress | completed",
|
||||
"priority": "high | medium | low",
|
||||
"startDate": "2025-01-01T00:00:00.000Z",
|
||||
"dueDate": "2025-01-15T00:00:00.000Z",
|
||||
"createdDate": "2025-01-01T00:00:00.000Z",
|
||||
"labels": ["string"],
|
||||
"tags": ["string"],
|
||||
"isStarred": false,
|
||||
"isImportant": false,
|
||||
"isTrashed": false,
|
||||
"isKanban": false,
|
||||
"routine": 0,
|
||||
"source": "todo | calendar | kanban"
|
||||
}
|
||||
],
|
||||
"total": 10
|
||||
}
|
||||
```
|
||||
|
||||
> **نکته:** Backend از `start_date`, `due_date`, `created_date`, `is_starred`, `is_important`, `is_trashed`, `is_kanban` (snake_case) استفاده میکند.
|
||||
|
||||
---
|
||||
|
||||
### ۳.۲ ایجاد Todo جدید
|
||||
|
||||
```
|
||||
POST /api/todos
|
||||
```
|
||||
|
||||
**Request Body (Backend - snake_case):**
|
||||
```json
|
||||
{
|
||||
"title": "string", // الزامی
|
||||
"description": "string", // اختیاری
|
||||
"status": 0, // اختیاری - 0: pending, 1: in-progress, 2: completed
|
||||
"priority": 1, // اختیاری - 0: high, 1: medium, 2: low
|
||||
"start_date": "string", // اختیاری - ISO 8601
|
||||
"due_date": "string", // اختیاری - ISO 8601
|
||||
"labels": ["string"], // اختیاری
|
||||
"tags": ["string"], // اختیاری
|
||||
"is_starred": false, // اختیاری
|
||||
"is_important": false, // اختیاری
|
||||
"is_kanban": false, // اختیاری
|
||||
"routine": 0 // اختیاری - 0|1|2|3|4
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"todo": {
|
||||
"id": 1,
|
||||
"title": "string",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۳.۳ بهروزرسانی Todo
|
||||
|
||||
```
|
||||
PUT /api/todos/{todoId}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|-----------|
|
||||
| `todoId`| number | شناسه Todo |
|
||||
|
||||
**Request Body (Backend - snake_case, همه اختیاری):**
|
||||
```json
|
||||
{
|
||||
"title": "string",
|
||||
"description": "string",
|
||||
"status": 0,
|
||||
"priority": 0,
|
||||
"start_date": "string",
|
||||
"due_date": "string",
|
||||
"labels": ["string"],
|
||||
"tags": ["string"],
|
||||
"is_starred": false,
|
||||
"is_important": false,
|
||||
"is_trashed": false,
|
||||
"is_kanban": false,
|
||||
"routine": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۳.۴ حذف Todo
|
||||
|
||||
```
|
||||
DELETE /api/todos/{todoId}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|-----------|
|
||||
| `todoId`| number | شناسه Todo |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۳.۵ لیست برچسبها
|
||||
|
||||
```
|
||||
GET /api/todos/labels
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"labels": ["Personal", "Work", "Urgent"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۴. Account Settings API (تنظیمات حساب)
|
||||
|
||||
### ۴.۱ دریافت اطلاعات کاربر (Account Details)
|
||||
|
||||
**سرویس:** `userManagementService`
|
||||
**استفاده در:** `AccountDetails.tsx`
|
||||
|
||||
```
|
||||
GET /api/admin/users/{userId}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|--------------|
|
||||
| `userId`| number | شناسه کاربر |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"id": 1,
|
||||
"role": "string",
|
||||
"email": "string",
|
||||
"status": "active | pending | inactive",
|
||||
"avatar": "string",
|
||||
"company": "string",
|
||||
"country": "string",
|
||||
"contact": "string",
|
||||
"fullName": "string",
|
||||
"username": "string",
|
||||
"currentPlan": "string",
|
||||
"billing": "string",
|
||||
"joinDate": "2025-01-01T00:00:00.000Z",
|
||||
"lastLogin": "2025-01-15T00:00:00.000Z",
|
||||
"twoStepVerification": false,
|
||||
"recentDevices": [],
|
||||
"activityTimeline": [],
|
||||
"projects": [],
|
||||
"invoices": [],
|
||||
"connections": [],
|
||||
"notifications": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **توجه:** AccountDetails از `userManagementService.getUser(authUser.id)` استفاده میکند و اطلاعات کاربر لاگینشده را نمایش میدهد. endpoint فعلی admin است؛ برای پروفایل کاربر لاگینشده ممکن است نیاز به `/api/users/me` باشد.
|
||||
|
||||
---
|
||||
|
||||
### ۴.۲ بهروزرسانی کاربر (Save Changes در Account Details)
|
||||
|
||||
**نکته:** دکمه "Save Changes" در AccountDetails در حال حاضر API call ندارد. برای ذخیره باید از endpoint زیر استفاده شود:
|
||||
|
||||
```
|
||||
PUT /api/admin/users/{userId}
|
||||
```
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| پارامتر | نوع | توضیحات |
|
||||
|---------|-------|--------------|
|
||||
| `userId`| number | شناسه کاربر |
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"fullName": "string",
|
||||
"email": "string",
|
||||
"contact": "string",
|
||||
"avatar": "string"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۴.۳ تغییر رمز عبور (Security)
|
||||
|
||||
**نکته:** کامپوننت `ChangePasswordCard` در حال حاضر API call ندارد. برای پیادهسازی:
|
||||
|
||||
```
|
||||
PUT /api/admin/users/{userId}/security
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"currentPassword": "string",
|
||||
"newPassword": "string",
|
||||
"twoStepVerification": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ۴.۴ Billing Plans (پلنها و صورتحساب)
|
||||
|
||||
**وضعیت فعلی:** از Server Actions با داده static استفاده میکند (`getPricingData`, `getInvoiceData`).
|
||||
|
||||
**APIهای پیشنهادی (در صورت استفاده از API واقعی):**
|
||||
|
||||
```
|
||||
GET ${API_URL}/pages/pricing
|
||||
```
|
||||
|
||||
**Response (فرمت مورد انتظار):**
|
||||
```json
|
||||
{
|
||||
"pricingPlans": [],
|
||||
"currentPlan": null
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
GET ${API_URL}/apps/invoice
|
||||
```
|
||||
|
||||
**Response (فرمت مورد انتظار):**
|
||||
```json
|
||||
{
|
||||
"invoices": [],
|
||||
"stats": {
|
||||
"totalInvoices": 0,
|
||||
"paidInvoices": 0,
|
||||
"pendingInvoices": 0,
|
||||
"pastDueInvoices": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## خلاصه Endpointها
|
||||
|
||||
| View | Method | Endpoint | استفاده |
|
||||
|------|--------|----------|---------|
|
||||
| Calendar | GET | `/api/events` | لیست رویدادها |
|
||||
| Calendar | POST | `/api/events` | ایجاد رویداد |
|
||||
| Calendar | PUT | `/api/events/{id}` | بهروزرسانی رویداد |
|
||||
| Calendar | DELETE | `/api/events/{id}` | حذف رویداد |
|
||||
| Kanban | GET | `/api/kanban/board` | دریافت Board |
|
||||
| Kanban | POST | `/api/kanban/columns` | ایجاد ستون |
|
||||
| Kanban | PUT | `/api/kanban/columns/{id}` | بهروزرسانی ستون |
|
||||
| Kanban | DELETE | `/api/kanban/columns/{id}` | حذف ستون |
|
||||
| Kanban | POST | `/api/kanban/tasks` | ایجاد تسک |
|
||||
| Kanban | PUT | `/api/kanban/tasks/{id}` | بهروزرسانی تسک |
|
||||
| Kanban | DELETE | `/api/kanban/tasks/{id}` | حذف تسک |
|
||||
| Kanban | PUT | `/api/kanban/tasks/{id}/move` | جابجایی تسک |
|
||||
| Todo | GET | `/api/todos` | لیست Todoها |
|
||||
| Todo | GET | `/api/todos/labels` | لیست برچسبها |
|
||||
| Todo | POST | `/api/todos` | ایجاد Todo |
|
||||
| Todo | PUT | `/api/todos/{id}` | بهروزرسانی Todo |
|
||||
| Todo | DELETE | `/api/todos/{id}` | حذف Todo |
|
||||
| Account | GET | `/api/admin/users/{id}` | دریافت اطلاعات کاربر |
|
||||
| Account | PUT | `/api/admin/users/{id}` | بهروزرسانی کاربر |
|
||||
| Account | PUT | `/api/admin/users/{id}/security` | تغییر رمز/تنظیمات امنیتی |
|
||||
|
||||
---
|
||||
|
||||
## تبدیلهای Frontend ↔ Backend
|
||||
|
||||
### Calendar
|
||||
- Frontend از `calendar` با مقادیر `Personal | Business | Family | Holiday | ETC` استفاده میکند.
|
||||
- `start` و `end` به صورت ISO 8601 ارسال میشوند.
|
||||
|
||||
### Kanban
|
||||
- **status:** `pending`→0, `in-progress`→1, `completed`→2
|
||||
- **priority:** `high`→0, `medium`→1, `low`→2
|
||||
- **snake_case در Backend:** `column_id`, `badge_text`, `due_date`
|
||||
|
||||
### Todo
|
||||
- **status:** `pending`→0, `in-progress`→1, `completed`→2
|
||||
- **priority:** `high`→0, `medium`→1, `low`→2
|
||||
- **snake_case در Backend:** `start_date`, `due_date`, `created_date`, `is_starred`, `is_important`, `is_trashed`, `is_kanban`
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
declare module 'tailwindcss-logical'
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
basePath: process.env.BASEPATH,
|
||||
output: 'standalone'
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
Generated
+11050
File diff suppressed because it is too large
Load Diff
+122
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"name": "vuexy-mui-nextjs-admin-template",
|
||||
"version": "4.0.0",
|
||||
"license": "Commercial",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
|
||||
"build:icons": "tsx src/assets/iconify-icons/bundle-icons-css.ts",
|
||||
"postinstall": "npm run build:icons",
|
||||
"removeI18n": "tsx src/remove-translation-scripts/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emoji-mart/data": "1.2.1",
|
||||
"@emoji-mart/react": "1.1.1",
|
||||
"@emotion/cache": "11.14.0",
|
||||
"@emotion/react": "11.14.0",
|
||||
"@emotion/styled": "11.14.0",
|
||||
"@floating-ui/react": "0.27.2",
|
||||
"@formatjs/intl-localematcher": "0.5.9",
|
||||
"@formkit/drag-and-drop": "0.2.6",
|
||||
"@fullcalendar/common": "5.11.5",
|
||||
"@fullcalendar/core": "6.1.15",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"@fullcalendar/interaction": "6.1.15",
|
||||
"@fullcalendar/list": "6.1.15",
|
||||
"@fullcalendar/react": "6.1.15",
|
||||
"@fullcalendar/timegrid": "6.1.15",
|
||||
"@hookform/resolvers": "3.9.1",
|
||||
"@mui/lab": "6.0.0-beta.19",
|
||||
"@mui/material": "6.2.1",
|
||||
"@mui/material-nextjs": "6.2.1",
|
||||
"@radix-ui/react-dialog": "1.1.4",
|
||||
"@reduxjs/toolkit": "2.5.0",
|
||||
"@tanstack/match-sorter-utils": "8.19.4",
|
||||
"@tanstack/react-table": "8.20.6",
|
||||
"@tiptap/extension-color": "^2.10.4",
|
||||
"@tiptap/extension-list-item": "^2.10.4",
|
||||
"@tiptap/extension-placeholder": "^2.10.4",
|
||||
"@tiptap/extension-text-align": "^2.10.4",
|
||||
"@tiptap/extension-text-style": "^2.10.4",
|
||||
"@tiptap/extension-underline": "^2.10.4",
|
||||
"@tiptap/pm": "^2.10.4",
|
||||
"@tiptap/react": "^2.10.4",
|
||||
"@tiptap/starter-kit": "^2.10.4",
|
||||
"apexcharts": "3.49.0",
|
||||
"bootstrap-icons": "1.11.3",
|
||||
"classnames": "2.5.1",
|
||||
"cmdk": "1.0.4",
|
||||
"date-fns": "4.1.0",
|
||||
"emoji-mart": "5.6.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"input-otp": "1.4.1",
|
||||
"keen-slider": "6.8.6",
|
||||
"mapbox-gl": "3.9.0",
|
||||
"negotiator": "1.0.0",
|
||||
"next": "15.1.2",
|
||||
"react": "18.3.1",
|
||||
"react-apexcharts": "1.4.1",
|
||||
"react-colorful": "5.6.1",
|
||||
"react-date-object": "1.1.9",
|
||||
"react-datepicker": "7.3.0",
|
||||
"react-dom": "18.3.1",
|
||||
"react-multi-date-picker": "4.4.2",
|
||||
"react-dropzone": "14.3.5",
|
||||
"react-hook-form": "7.54.1",
|
||||
"react-map-gl": "7.1.8",
|
||||
"react-perfect-scrollbar": "1.5.8",
|
||||
"react-player": "2.16.0",
|
||||
"react-redux": "9.2.0",
|
||||
"react-toastify": "10.0.6",
|
||||
"react-use": "17.6.0",
|
||||
"recharts": "2.15.0",
|
||||
"server-only": "0.0.1",
|
||||
"valibot": "0.42.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "2.2.286",
|
||||
"@iconify/tools": "4.1.1",
|
||||
"@iconify/types": "2.0.0",
|
||||
"@iconify/utils": "2.2.1",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/mapbox-gl": "^3.4.1",
|
||||
"@types/negotiator": "^0.6.3",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||
"@typescript-eslint/parser": "7.18.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"consola": "3.3.0",
|
||||
"dotenv-cli": "7.4.4",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-next": "15.1.2",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-import-resolver-typescript": "3.7.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"globby": "14.0.2",
|
||||
"postcss": "8.4.49",
|
||||
"postcss-styled-syntax": "0.7.0",
|
||||
"prettier": "3.4.2",
|
||||
"stylelint": "16.12.0",
|
||||
"stylelint-use-logical-spec": "5.0.1",
|
||||
"stylis": "4.3.4",
|
||||
"stylis-plugin-rtl": "2.1.1",
|
||||
"tailwindcss": "3.4.17",
|
||||
"tailwindcss-logical": "3.0.1",
|
||||
"tsx": "4.19.2",
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"rimraf": "^5.0.7",
|
||||
"@tiptap/core": "^2.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
"rimraf": "^5.0.7"
|
||||
}
|
||||
}
|
||||
Generated
+8663
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
*
|
||||
* Name: Paykan Fonts
|
||||
* Version: 1.2
|
||||
* Author: Reza Bakhtiarifard (rbakhtiarifard.ir)
|
||||
* Created on: Nov 2023
|
||||
* Updated on: Nov 2023
|
||||
* Vendor: http://fontiran.com
|
||||
* Copyright: Commercial/Proprietary Software
|
||||
--------------------------------------------------------------------------------------
|
||||
فونت پیکان یک نرم افزار مالکیتی محسوب می شود. جهت آگاهی از قوانین استفاده از این فونت ها لطفا به وب سایت (فونت ایران دات کام) مراجعه نمایید
|
||||
--------------------------------------------------------------------------------------
|
||||
Paykan fonts are considered a proprietary software. To gain information about the laws regarding the use of these fonts, please visit www.fontiran.com
|
||||
--------------------------------------------------------------------------------------
|
||||
This set of fonts are used in this project under the license: (.....)
|
||||
------------------------------------------------------------------------------------- fonts/-
|
||||
*
|
||||
**/
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan FaNum;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('woff/PaykanFaNum-thin.woff') format('woff'),
|
||||
url('woff2/PaykanFaNum-thin.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan FaNum;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('woff/PaykanFaNum-Light.woff') format('woff'),
|
||||
url('woff2/PaykanFaNum-Light.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan FaNum;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url('woff/PaykanFaNum-Regular.woff') format('woff'),
|
||||
url('woff2/PaykanFaNum-Regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan FaNum;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url('woff/PaykanFaNum-Bold.woff') format('woff'),
|
||||
url('woff2/PaykanFaNum-Bold.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan FaNum;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('woff/PaykanFaNum-Black.woff') format('woff'),
|
||||
url('woff2/PaykanFaNum-Black.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan FaNum;
|
||||
font-style: normal;
|
||||
font-weight: 950;
|
||||
src: url('woff/PaykanFaNum-ExtraBlack.woff') format('woff'),
|
||||
url('woff2/PaykanFaNum-ExtraBlack.woff2') format('woff2');
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<head>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9" />
|
||||
<title>Paykan</title>
|
||||
<style type="text/css" media="screen">
|
||||
@font-face { font-family: 'PaykanFaNum Light WOFF'; src: url('woff/PaykanFaNum-Light.woff'); }
|
||||
@font-face { font-family: 'PaykanFaNum ExtraBlack WOFF'; src: url('woff/PaykanFaNum-ExtraBlack.woff'); }
|
||||
@font-face { font-family: 'PaykanFaNum Light WOFF2'; src: url('woff2/PaykanFaNum-Light.woff2'); }
|
||||
@font-face { font-family: 'PaykanFaNum ExtraBlack WOFF2'; src: url('woff2/PaykanFaNum-ExtraBlack.woff2'); }
|
||||
|
||||
|
||||
body {
|
||||
font-family: "PaykanFaNum Light WOFF";
|
||||
font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
-moz-font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
-webkit-font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
-ms-font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
-o-font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
}
|
||||
p { padding: 15px; margin: 10px; }
|
||||
.features {
|
||||
font-size:x-small;
|
||||
font:sans-serif;
|
||||
}
|
||||
.label{
|
||||
font-family: sans-serif;
|
||||
font-size: x-small;
|
||||
color: grey;
|
||||
}
|
||||
span#p08 { font-size: 8pt; }
|
||||
span#p10 { font-size: 10pt; }
|
||||
span#p12 { font-size: 12pt; }
|
||||
span#p14 { font-size: 14pt; }
|
||||
span#p18 { font-size: 18pt; }
|
||||
span#p36 { font-size: 36pt; }
|
||||
span#p72 { font-size: 72pt; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function updateParagraph() {
|
||||
// update paragraph text based on user input:
|
||||
var txt = document.getElementById('textInput');
|
||||
var paragraphs = ['p08','p10','p12','p14','p18','p36','p72'];
|
||||
for (i = 0; i < paragraphs.length; i++) {
|
||||
paragraphID = paragraphs[i];
|
||||
var paragraph = document.getElementById(paragraphID);
|
||||
paragraph.textContent = txt.value;
|
||||
}
|
||||
}
|
||||
function updateFeatures() {
|
||||
// update features based on user input:
|
||||
// first, get feature on/off line:
|
||||
var cssCode = "";
|
||||
var codeLine = "";
|
||||
var checkboxes = document.getElementsByClassName("otFeature")
|
||||
for (i = 0; i < checkboxes.length; i++) {
|
||||
var checkbox = checkboxes[i];
|
||||
codeLine += '"'+checkbox.name+'" ';
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
if (checkbox.name=="kern") {
|
||||
cssCode += "font-kerning: "
|
||||
cssCode += checkbox.checked ? 'normal; ' : 'none; ';
|
||||
} else if (checkbox.name=="liga") {
|
||||
codeLine += '"clig" '
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'common-ligatures contextual; ' : 'no-common-ligatures no-contextual; ';
|
||||
} else if (checkbox.name=="dlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'discretionary-ligatures; ' : 'no-discretionary-ligatures; ';
|
||||
} else if (checkbox.name=="hlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'historical-ligatures; ' : 'no-historical-ligatures; ';
|
||||
}
|
||||
}
|
||||
codeLine = codeLine.slice(0, -2)
|
||||
|
||||
// then, apply line for every browser:
|
||||
var prefixes = ["","-moz-","-webkit-","-ms-","-o-",];
|
||||
var suffix = "font-feature-settings: "
|
||||
for (i = 0; i < prefixes.length; i++) {
|
||||
var prefix = prefixes[i];
|
||||
cssCode += prefix
|
||||
cssCode += suffix
|
||||
cssCode += codeLine
|
||||
cssCode += "; "
|
||||
}
|
||||
|
||||
document.getElementById('fontTestBody').style.cssText = cssCode;
|
||||
document.getElementById('featureLine').innerHTML = cssCode.replace(/;/g,";<br/>");
|
||||
changeFont();
|
||||
}
|
||||
function changeFont() {
|
||||
var selector = document.getElementById('fontFamilySelector');
|
||||
var selected_index = selector.selectedIndex;
|
||||
var selected_option_text = selector.options[selected_index].text;
|
||||
document.getElementById('fontTestBody').style.fontFamily = selected_option_text;
|
||||
}
|
||||
function setDefaultText(defaultText) {
|
||||
document.getElementById('textInput').value = decodeEntities(defaultText);
|
||||
updateParagraph();
|
||||
}
|
||||
function setLat1() {
|
||||
var lat1 = "abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLM NOPQRSTUVWXYZ ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØŒÞÙÚÛÜÝŸ àáâãäåæçèéêëìíîïðñòóôõöøœþßùúûüýÿ .,:;·…¿?¡!«»‹› /|¦\()[]{}_-–—‚„‘’“”"' #&§@•­*†‡¶ +×÷±=<>¬μ ^~´`ˆ¯˜¨¸ ¥€£$¢¤ƒ ™®© 1234567890 ªº°%‰ ¹²³¼½¾";
|
||||
return setDefaultText(lat1);
|
||||
}
|
||||
function setCharset() {
|
||||
var completeCharSet = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z ء ا ﺎ أ ﺄ إ ﺈ آ ﺂ ٱ ﭑ ٮ ب ﺐ ﺒ ﺑ پ ﭗ ﭙ ﭘ ت ﺖ ﺘ ﺗ ث ﺚ ﺜ ﺛ ج ﺞ ﺠ ﺟ چ ﭻ ﭽ ﭼ ح ﺢ ﺤ ﺣ خ ﺦ ﺨ ﺧ د ﺪ ذ ﺬ ر ﺮ ز ﺰ ژ ﮋ س ﺲ ﺴ ﺳ ش ﺶ ﺸ ﺷ ص ﺺ ﺼ ﺻ ض ﺾ ﻀ ﺿ ط ﻂ ﻄ ﻃ ظ ﻆ ﻈ ﻇ ع ﻊ ﻌ ﻋ غ ﻎ ﻐ ﻏ ف ﻒ ﻔ ﻓ ڤ ﭫ ﭭ ﭬ ڡ ٯ ق ﻖ ﻘ ﻗ ك ﻚ ﻜ ﻛ ک ﮏ ﮑ ﮐ گ ﮓ ﮕ ﮔ ل ﻞ ﻠ ﻟ م ﻢ ﻤ ﻣ ن ﻦ ﻨ ﻧ ں ﮟ ه ﻪ ﻬ ﻫ ۀ ﮤ ﮥ ة ﺔ و ﻮ ؤ ﺆ ى ﻰ ي ﻲ ﻴ ﻳ ئ ﺊ ﺌ ﺋ ی ﯽ ﯿ ﯾ ـ ﻻ ﻼ ﻷ ﻸ ﻹ ﻺ ﻵ ﻶ ﷲ 0 1 2 3 4 5 6 7 8 9 ٫ ٬ ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ . , : ; ! ? * # / \ ( ) { } [ ] - _ „ “ ” ‘ ’ « » ‹ › " ' ، ؛ ؟ ٭ ﴾ ﴿         ​ ‍ ‌ 
  ﷼ $ + − × ÷ = ≠ % @ & ٪ ﮲ ﮳ ﮴ ﮵ ﮹ ﮶ ٰ ٖ ٔ ٕ ً ٌ ٍ َ ُ ِ ّ ْ ٓ";
|
||||
setDefaultText(completeCharSet);
|
||||
}
|
||||
function decodeEntities(string){
|
||||
var elem = document.createElement('div');
|
||||
elem.innerHTML = string;
|
||||
return elem.textContent;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body id="fontTestBody">
|
||||
<select size="1" id="fontFamilySelector" name="fontFamilySelector" onchange="changeFont()">
|
||||
<option value="PaykanFaNum-Light.woff2">PaykanFaNum Light WOFF</option>
|
||||
<option value="PaykanFaNum-ExtraBlack.woff2">PaykanFaNum ExtraBlack WOFF</option>
|
||||
<option value="PaykanFaNum-Light.woff2">PaykanFaNum Light WOFF2</option>
|
||||
<option value="PaykanFaNum-ExtraBlack.woff2">PaykanFaNum ExtraBlack WOFF2</option>
|
||||
|
||||
|
||||
</select>
|
||||
<input type="text" value="برای تست اینجا تایپ کنید." id="textInput" onclick="this.select();" onkeyup="updateParagraph()" size="80" />
|
||||
<p class="features">
|
||||
<a href="javascript:setCharset();">Charset</a>
|
||||
|
||||
 
|
||||
|
||||
|
||||
</p>
|
||||
<p class="features" id="featureLine" style="display:none;">font-feature-settings: "kern" on, "liga" on, "dlig" on;</p>
|
||||
<p><span class="label">8 pt.</span> <span id="p08">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">10 pt.</span> <span id="p10">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">12 pt.</span> <span id="p12">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">14 pt.</span> <span id="p14">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">18 pt.</span> <span id="p18">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">36 pt.</span> <span id="p36">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">72 pt.</span> <span id="p72">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
|
||||
</span></p>
|
||||
|
||||
</body>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
*
|
||||
* Name: Paykan Fonts
|
||||
* Version: 1.2
|
||||
* Author: Reza Bakhtiarifard (rbakhtiarifard.ir)
|
||||
* Created on: Nov 2023
|
||||
* Updated on: Nov 2023
|
||||
* Vendor: http://fontiran.com
|
||||
* Copyright: Commercial/Proprietary Software
|
||||
--------------------------------------------------------------------------------------
|
||||
فونت پیکان یک نرم افزار مالکیتی محسوب می شود. جهت آگاهی از قوانین استفاده از این فونت ها لطفا به وب سایت (فونت ایران دات کام) مراجعه نمایید
|
||||
--------------------------------------------------------------------------------------
|
||||
Paykan fonts are considered a proprietary software. To gain information about the laws regarding the use of these fonts, please visit www.fontiran.com
|
||||
--------------------------------------------------------------------------------------
|
||||
This set of fonts are used in this project under the license: (.....)
|
||||
------------------------------------------------------------------------------------- fonts/-
|
||||
*
|
||||
**/
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('woff/Paykan-thin.woff') format('woff'),
|
||||
url('woff2/Paykan-thin.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('woff/Paykan-Light.woff') format('woff'),
|
||||
url('woff2/Paykan-Light.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url('woff/Paykan-Regular.woff') format('woff'),
|
||||
url('woff2/Paykan-Regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url('woff/Paykan-Bold.woff') format('woff'),
|
||||
url('woff2/Paykan-Bold.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('woff/Paykan-Black.woff') format('woff'),
|
||||
url('woff2/Paykan-Black.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Paykan;
|
||||
font-style: normal;
|
||||
font-weight: 950;
|
||||
src: url('woff/Paykan-ExtraBlack.woff') format('woff'),
|
||||
url('woff2/Paykan-ExtraBlack.woff2') format('woff2');
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<head>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9" />
|
||||
<title>Paykan</title>
|
||||
<style type="text/css" media="screen">
|
||||
@font-face { font-family: 'Paykan Light WOFF'; src: url('woff/Paykan-Light.woff'); }
|
||||
@font-face { font-family: 'Paykan ExtraBlack WOFF'; src: url('woff/Paykan-ExtraBlack.woff'); }
|
||||
@font-face { font-family: 'Paykan Light WOFF2'; src: url('woff2/Paykan-Light.woff2'); }
|
||||
@font-face { font-family: 'Paykan ExtraBlack WOFF2'; src: url('woff2/Paykan-ExtraBlack.woff2'); }
|
||||
|
||||
|
||||
body {
|
||||
font-family: "Paykan Light WOFF";
|
||||
font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
-moz-font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
-webkit-font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
-ms-font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
-o-font-feature-settings: "kern" on, "liga" on, "dlig" on;
|
||||
}
|
||||
p { padding: 15px; margin: 10px; }
|
||||
.features {
|
||||
font-size:x-small;
|
||||
font:sans-serif;
|
||||
}
|
||||
.label{
|
||||
font-family: sans-serif;
|
||||
font-size: x-small;
|
||||
color: grey;
|
||||
}
|
||||
span#p08 { font-size: 8pt; }
|
||||
span#p10 { font-size: 10pt; }
|
||||
span#p12 { font-size: 12pt; }
|
||||
span#p14 { font-size: 14pt; }
|
||||
span#p18 { font-size: 18pt; }
|
||||
span#p36 { font-size: 36pt; }
|
||||
span#p72 { font-size: 72pt; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function updateParagraph() {
|
||||
// update paragraph text based on user input:
|
||||
var txt = document.getElementById('textInput');
|
||||
var paragraphs = ['p08','p10','p12','p14','p18','p36','p72'];
|
||||
for (i = 0; i < paragraphs.length; i++) {
|
||||
paragraphID = paragraphs[i];
|
||||
var paragraph = document.getElementById(paragraphID);
|
||||
paragraph.textContent = txt.value;
|
||||
}
|
||||
}
|
||||
function updateFeatures() {
|
||||
// update features based on user input:
|
||||
// first, get feature on/off line:
|
||||
var cssCode = "";
|
||||
var codeLine = "";
|
||||
var checkboxes = document.getElementsByClassName("otFeature")
|
||||
for (i = 0; i < checkboxes.length; i++) {
|
||||
var checkbox = checkboxes[i];
|
||||
codeLine += '"'+checkbox.name+'" ';
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
if (checkbox.name=="kern") {
|
||||
cssCode += "font-kerning: "
|
||||
cssCode += checkbox.checked ? 'normal; ' : 'none; ';
|
||||
} else if (checkbox.name=="liga") {
|
||||
codeLine += '"clig" '
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'common-ligatures contextual; ' : 'no-common-ligatures no-contextual; ';
|
||||
} else if (checkbox.name=="dlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'discretionary-ligatures; ' : 'no-discretionary-ligatures; ';
|
||||
} else if (checkbox.name=="hlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'historical-ligatures; ' : 'no-historical-ligatures; ';
|
||||
}
|
||||
}
|
||||
codeLine = codeLine.slice(0, -2)
|
||||
|
||||
// then, apply line for every browser:
|
||||
var prefixes = ["","-moz-","-webkit-","-ms-","-o-",];
|
||||
var suffix = "font-feature-settings: "
|
||||
for (i = 0; i < prefixes.length; i++) {
|
||||
var prefix = prefixes[i];
|
||||
cssCode += prefix
|
||||
cssCode += suffix
|
||||
cssCode += codeLine
|
||||
cssCode += "; "
|
||||
}
|
||||
|
||||
document.getElementById('fontTestBody').style.cssText = cssCode;
|
||||
document.getElementById('featureLine').innerHTML = cssCode.replace(/;/g,";<br/>");
|
||||
changeFont();
|
||||
}
|
||||
function changeFont() {
|
||||
var selector = document.getElementById('fontFamilySelector');
|
||||
var selected_index = selector.selectedIndex;
|
||||
var selected_option_text = selector.options[selected_index].text;
|
||||
document.getElementById('fontTestBody').style.fontFamily = selected_option_text;
|
||||
}
|
||||
function setDefaultText(defaultText) {
|
||||
document.getElementById('textInput').value = decodeEntities(defaultText);
|
||||
updateParagraph();
|
||||
}
|
||||
function setLat1() {
|
||||
var lat1 = "abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLM NOPQRSTUVWXYZ ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØŒÞÙÚÛÜÝŸ àáâãäåæçèéêëìíîïðñòóôõöøœþßùúûüýÿ .,:;·…¿?¡!«»‹› /|¦\()[]{}_-–—‚„‘’“”"' #&§@•­*†‡¶ +×÷±=<>¬μ ^~´`ˆ¯˜¨¸ ¥€£$¢¤ƒ ™®© 1234567890 ªº°%‰ ¹²³¼½¾";
|
||||
return setDefaultText(lat1);
|
||||
}
|
||||
function setCharset() {
|
||||
var completeCharSet = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z ء ا ﺎ أ ﺄ إ ﺈ آ ﺂ ٱ ﭑ ٮ ب ﺐ ﺒ ﺑ پ ﭗ ﭙ ﭘ ت ﺖ ﺘ ﺗ ث ﺚ ﺜ ﺛ ج ﺞ ﺠ ﺟ چ ﭻ ﭽ ﭼ ح ﺢ ﺤ ﺣ خ ﺦ ﺨ ﺧ د ﺪ ذ ﺬ ر ﺮ ز ﺰ ژ ﮋ س ﺲ ﺴ ﺳ ش ﺶ ﺸ ﺷ ص ﺺ ﺼ ﺻ ض ﺾ ﻀ ﺿ ط ﻂ ﻄ ﻃ ظ ﻆ ﻈ ﻇ ع ﻊ ﻌ ﻋ غ ﻎ ﻐ ﻏ ف ﻒ ﻔ ﻓ ڤ ﭫ ﭭ ﭬ ڡ ٯ ق ﻖ ﻘ ﻗ ك ﻚ ﻜ ﻛ ک ﮏ ﮑ ﮐ گ ﮓ ﮕ ﮔ ل ﻞ ﻠ ﻟ م ﻢ ﻤ ﻣ ن ﻦ ﻨ ﻧ ں ﮟ ه ﻪ ﻬ ﻫ ۀ ﮤ ﮥ ة ﺔ و ﻮ ؤ ﺆ ى ﻰ ي ﻲ ﻴ ﻳ ئ ﺊ ﺌ ﺋ ی ﯽ ﯿ ﯾ ـ ﻻ ﻼ ﻷ ﻸ ﻹ ﻺ ﻵ ﻶ ﷲ 0 1 2 3 4 5 6 7 8 9 ٫ ٬ ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ . , : ; ! ? * # / \ ( ) { } [ ] - _ „ “ ” ‘ ’ « » ‹ › " ' ، ؛ ؟ ٭ ﴾ ﴿         ​ ‍ ‌ 
  ﷼ $ + − × ÷ = ≠ % @ & ٪ ﮲ ﮳ ﮴ ﮵ ﮹ ﮶ ٰ ٖ ٔ ٕ ً ٌ ٍ َ ُ ِ ّ ْ ٓ";
|
||||
setDefaultText(completeCharSet);
|
||||
}
|
||||
function decodeEntities(string){
|
||||
var elem = document.createElement('div');
|
||||
elem.innerHTML = string;
|
||||
return elem.textContent;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body id="fontTestBody">
|
||||
<select size="1" id="fontFamilySelector" name="fontFamilySelector" onchange="changeFont()">
|
||||
<option value="Paykan-Light.woff2">Paykan Light WOFF</option>
|
||||
<option value="Paykan-ExtraBlack.woff2">Paykan ExtraBlack WOFF</option>
|
||||
<option value="Paykan-Light.woff2">Paykan Light WOFF2</option>
|
||||
<option value="Paykan-ExtraBlack.woff2">Paykan ExtraBlack WOFF2</option>
|
||||
|
||||
|
||||
</select>
|
||||
<input type="text" value="برای تست اینجا تایپ کنید." id="textInput" onclick="this.select();" onkeyup="updateParagraph()" size="80" />
|
||||
<p class="features">
|
||||
<a href="javascript:setCharset();">Charset</a>
|
||||
|
||||
 
|
||||
|
||||
|
||||
</p>
|
||||
<p class="features" id="featureLine" style="display:none;">font-feature-settings: "kern" on, "liga" on, "dlig" on;</p>
|
||||
<p><span class="label">8 pt.</span> <span id="p08">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">10 pt.</span> <span id="p10">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">12 pt.</span> <span id="p12">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">14 pt.</span> <span id="p14">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">18 pt.</span> <span id="p18">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">36 pt.</span> <span id="p36">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
|
||||
</span></p>
|
||||
|
||||
<p><span class="label">72 pt.</span> <span id="p72">
|
||||
بنشین بر لب جوی و گذر عمر ببین - کاین اشارت ز جهان گذران ما را بس
|
||||
|
||||
</span></p>
|
||||
|
||||
</body>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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,10 @@
|
||||
/* Webfont: GoftehWeb-Heavy */
|
||||
@font-face {
|
||||
font-family: 'edameh';
|
||||
src: url('fonts/edamehFaNumWeb-ExtraBlack.woff') format('woff'),
|
||||
url('fonts/edamehFaNumWeb-ExtraBlack.woff2') format('woff2');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta content="en-us" http-equiv="Content-Language"/>
|
||||
<title>Edameh ExtraBlack - Web Font Specimen</title>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="edameh.css" />
|
||||
<style type="text/css" media="screen">
|
||||
body { font-size: 42px; font-family: "edameh", calibri; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p contenteditable="true">متنی که دوست داری رو اینجا بنویس</p>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
/* Webfont: GoftehWeb-Heavy */
|
||||
@font-face {
|
||||
font-family: 'edameh';
|
||||
src: url('fonts/edamehNoEnWeb-ExtraBlack.woff') format('woff'),
|
||||
url('fonts/edamehNoEnWeb-ExtraBlack.woff2') format('woff2');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta content="en-us" http-equiv="Content-Language"/>
|
||||
<title>Edameh ExtraBlack - Web Font Specimen</title>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="edameh.css" />
|
||||
<style type="text/css" media="screen">
|
||||
body { font-size: 42px; font-family: "edameh", calibri; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p contenteditable="true">متنی که دوست داری رو اینجا بنویس</p>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
/* Webfont: GoftehWeb-Heavy */
|
||||
@font-face {
|
||||
font-family: 'edameh';
|
||||
src: url('fonts/edamehWeb-ExtraBlack.woff') format('woff'),
|
||||
url('fonts/edamehWeb-ExtraBlack.woff2') format('woff2');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta content="en-us" http-equiv="Content-Language"/>
|
||||
<title>Edameh ExtraBlack - Web Font Specimen</title>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="edameh.css" />
|
||||
<style type="text/css" media="screen">
|
||||
body { font-size: 42px; font-family: "edameh", calibri; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p contenteditable="true">متنی که دوست داری رو اینجا بنویس</p>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,418 @@
|
||||
<head>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9" />
|
||||
<title>Melli</title>
|
||||
<style type="text/css" media="screen">
|
||||
@font-face { font-family: 'WOFF Melli_FaNum'; src: url('Melli_FaNum.woff'); }
|
||||
@font-face { font-family: 'WOFF2 Melli_FaNum'; src: url('Melli_FaNum.woff2'); }
|
||||
|
||||
body {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
.features, .label, a, #controls {
|
||||
font: normal normal normal small sans-serif;
|
||||
}
|
||||
.features .emojiButton {
|
||||
vertical-align: -5%;
|
||||
font-size: small;
|
||||
}
|
||||
.emojiButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
#flexbox {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
}
|
||||
#controls {
|
||||
flex: 0 1 auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
border: 0px solid transparent;
|
||||
height: auto;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
#metricsLine {
|
||||
background-color: #EEE;
|
||||
border-top: 1px solid #AAA;
|
||||
border-bottom: 1px solid #AAA;
|
||||
width: 100%;
|
||||
margin: 0.2em 0;
|
||||
padding: 0 0;
|
||||
font-size: 2em;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
text-overflow: none;
|
||||
display: none;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
}
|
||||
#metricsLine::-webkit-scrollbar { /* WebKit */
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
#waterfall {
|
||||
flex: 1 1 auto;
|
||||
border: 0 solid transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
color: black;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
font-family: "WOFF Melli_FaNum";
|
||||
font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-moz-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-webkit-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-ms-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-o-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
}
|
||||
div, p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#waterfall p {
|
||||
margin-bottom: 0.8em;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.○ .sampletext {
|
||||
-webkit-text-stroke: 1px black;
|
||||
-webkit-text-fill-color: #FFF0;
|
||||
}
|
||||
.features, .label, a {
|
||||
color: #888;
|
||||
}
|
||||
.label {
|
||||
background-color: #ddd;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
span#p08 { font-size: 08pt; padding: 08pt 0; }
|
||||
span#p09 { font-size: 09pt; padding: 09pt 0; }
|
||||
span#p10 { font-size: 10pt; padding: 10pt 0; }
|
||||
span#p11 { font-size: 11pt; padding: 11pt 0; }
|
||||
span#p12 { font-size: 12pt; padding: 12pt 0; }
|
||||
span#p13 { font-size: 13pt; padding: 13pt 0; }
|
||||
span#p14 { font-size: 14pt; padding: 14pt 0; }
|
||||
span#p15 { font-size: 15pt; padding: 15pt 0; }
|
||||
span#p16 { font-size: 16pt; padding: 16pt 0; }
|
||||
span#largeParagraph { font-size: 32pt; padding: 32pt 0; }
|
||||
span#veryLargeParagraph { font-size: 100pt; padding: 100pt 0; }
|
||||
|
||||
.otFeatureLabel {
|
||||
color: #666;
|
||||
background-color: #ddd;
|
||||
padding: 0.2em 0.5em 0.3em 0.5em;
|
||||
margin: 0 .04em;
|
||||
line-height: 2em;
|
||||
border-radius: 0.3em;
|
||||
border: 0;
|
||||
text-align:center;
|
||||
}
|
||||
.otFeatureLabel, .otFeature {
|
||||
position: relative;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.otFeatureLabel {
|
||||
padding: 0.2em 0.5em 0.3em 0.5em;
|
||||
margin: 0 .04em;
|
||||
line-height: 2em;
|
||||
color: #666;
|
||||
background-color: #ddd;
|
||||
border-radius: 0.3em;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
z-index: 6;
|
||||
}
|
||||
.wrapper {
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
select {
|
||||
float: left;
|
||||
margin: 0 0.5em 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
input[type=text] {
|
||||
border: 1px solid #999;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.features {
|
||||
clear: left;
|
||||
}
|
||||
input[type=checkbox]:checked + label {
|
||||
visibility: visible;
|
||||
color: #fff;
|
||||
background-color: #888;
|
||||
}
|
||||
.otFeature {
|
||||
visibility: collapse;
|
||||
margin: 0 -1em 0 0;
|
||||
}
|
||||
.otFeatureLabel .tooltip {
|
||||
visibility: hidden;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 0px 5px;
|
||||
top: -2em;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
}
|
||||
.otFeatureLabel:hover .tooltip {
|
||||
visibility: visible;
|
||||
}
|
||||
#featureLine {
|
||||
display: none;
|
||||
border-bottom: 1px solid #999;
|
||||
padding: 0.5em 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
/* Footer paragraph: */
|
||||
#helptext {
|
||||
color: black;
|
||||
background-color: #ddd;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding: 2px
|
||||
width: 100%;
|
||||
font: x-small sans-serif;
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #333;
|
||||
}
|
||||
.features, .label, a, body, p, #metricsLine {
|
||||
color: white;
|
||||
}
|
||||
.label {
|
||||
background-color: black;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
.otFeatureLabel, input[type=text] {
|
||||
color: white;
|
||||
background-color: black;
|
||||
}
|
||||
input[type=checkbox]:checked + label {
|
||||
color: black;
|
||||
background-color: #aaa;
|
||||
}
|
||||
#helptext {
|
||||
background-color: #777;
|
||||
}
|
||||
.○ .sampletext {
|
||||
-webkit-text-stroke: 1px white;
|
||||
-webkit-text-fill-color: #0000;
|
||||
}
|
||||
#metricsLine {
|
||||
background-color: #222;
|
||||
border-color: #777;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="document.getElementById('textInput').focus();setCharset();">
|
||||
<div id="flexbox">
|
||||
<div id="controls">
|
||||
<div>
|
||||
<select size="1" id="fontFamilySelector" name="fontFamilySelector" onchange="changeFont()">
|
||||
<option value="Melli_FaNum.woff">WOFF Melli_FaNum</option>
|
||||
<option value="Melli_FaNum.woff2">WOFF2 Melli_FaNum</option>
|
||||
</select>
|
||||
<div class="wrapper" spellcheck="false">
|
||||
<input type="text" value="Type Text Here." id="textInput" onclick="this.select();" onkeyup="updateParagraph()" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="features">
|
||||
<a href="javascript:setCharset();">Charset</a>
|
||||
<a href="javascript:setLat1();">Lat1</a>
|
||||
 
|
||||
<a href="https://caniuse.com/#feat=woff">woff</a>
|
||||
<a href="https://caniuse.com/#feat=woff2">woff2</a>
|
||||
 
|
||||
<a onclick="toggleInverse();" id="invert" class="emojiButton">🔲</a>
|
||||
<label><input type="checkbox" id="kern" value="kern" class="otFeature" onchange="updateFeatures()" checked><label for="kern" class="otFeatureLabel">kern</label>
|
||||
<label><input type="checkbox" id="liga" value="liga" class="otFeature" onchange="updateFeatures()" checked><label for="liga" class="otFeatureLabel">liga/clig</label>
|
||||
<label><input type="checkbox" id="calt" value="calt" class="otFeature" onchange="updateFeatures()" checked><label for="calt" class="otFeatureLabel">calt</label>
|
||||
<input type="checkbox" id="init" value="init" class="otFeature" onchange="updateFeatures()"><label for="init" class="otFeatureLabel">init</label>
|
||||
<input type="checkbox" id="medi" value="medi" class="otFeature" onchange="updateFeatures()"><label for="medi" class="otFeatureLabel">medi</label>
|
||||
<input type="checkbox" id="fina" value="fina" class="otFeature" onchange="updateFeatures()"><label for="fina" class="otFeatureLabel">fina</label>
|
||||
<input type="checkbox" id="rlig" value="rlig" class="otFeature" onchange="updateFeatures()"><label for="rlig" class="otFeatureLabel">rlig</label>
|
||||
<input type="checkbox" id="dlig" value="dlig" class="otFeature" onchange="updateFeatures()"><label for="dlig" class="otFeatureLabel">dlig</label>
|
||||
<input type="checkbox" id="ss01" value="ss01" class="otFeature" onchange="updateFeatures()"><label for="ss01" class="otFeatureLabel">ss01</label>
|
||||
<input type="checkbox" id="ss02" value="ss02" class="otFeature" onchange="updateFeatures()"><label for="ss02" class="otFeatureLabel">ss02</label>
|
||||
<input type="checkbox" id="ss03" value="ss03" class="otFeature" onchange="updateFeatures()"><label for="ss03" class="otFeatureLabel">ss03</label>
|
||||
<input type="checkbox" id="ss04" value="ss04" class="otFeature" onchange="updateFeatures()"><label for="ss04" class="otFeatureLabel">ss04</label>
|
||||
<label><input type="checkbox" value="show" onchange="updateFeatures();document.getElementById('featureLine').style.display=this.checked?'block':'none'">CSS</label>
|
||||
<label><input type="checkbox" value="show" onchange="updateFeatures();document.getElementById('metricsLine').style.display=this.checked?'block':'none'">Metrics</label>
|
||||
</p>
|
||||
<p class="features" id="featureLine">font-feature-settings: "kern" on, "liga" on, "calt" on;</p>
|
||||
</div>
|
||||
<div id="waterfall" class="●">
|
||||
<div id="metricsLine"></div>
|
||||
<p><span class="label">08</span> <span class="sampletext" id="p08"></span></p>
|
||||
<p><span class="label">09</span> <span class="sampletext" id="p09"></span></p>
|
||||
<p><span class="label">10</span> <span class="sampletext" id="p10"></span></p>
|
||||
<p><span class="label">11</span> <span class="sampletext" id="p11"></span></p>
|
||||
<p><span class="label">12</span> <span class="sampletext" id="p12"></span></p>
|
||||
<p><span class="label">13</span> <span class="sampletext" id="p13"></span></p>
|
||||
<p><span class="label">14</span> <span class="sampletext" id="p14"></span></p>
|
||||
<p><span class="label">15</span> <span class="sampletext" id="p15"></span></p>
|
||||
<p><span class="label">16</span> <span class="sampletext" id="p16"></span></p>
|
||||
<p><span class="sampletext" id="largeParagraph"></span></p>
|
||||
<p><span class="sampletext" id="veryLargeParagraph"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Disclaimer -->
|
||||
<p id="helptext" onmouseleave="vanish(this);">
|
||||
Ctrl-R: Reset Charset. Ctrl-L: Latin1. Ctrl-J: LTR/RTL. Ctrl-comma/period: step through fonts. Pull mouse across this note to make it disappear.
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
const selector = document.getElementById("fontFamilySelector");
|
||||
const selectorOptions = selector.options;
|
||||
const selectorLength = selectorOptions.length;
|
||||
|
||||
document.addEventListener('keyup', keyAnalysis);
|
||||
|
||||
function keyAnalysis(event) {
|
||||
if (event.ctrlKey) {
|
||||
if (event.code == 'KeyR') {
|
||||
setCharset();
|
||||
} else if (event.code == 'KeyL') {
|
||||
setLat1();
|
||||
} else if (event.code == 'KeyJ') {
|
||||
toggleLeftRight();
|
||||
} else if (event.code == 'Period') {
|
||||
selector.selectedIndex = (selector.selectedIndex + 1) % selectorLength;
|
||||
changeFont();
|
||||
} else if (event.code == 'Comma') {
|
||||
var newIndex = selector.selectedIndex - 1;
|
||||
if (newIndex<0) {
|
||||
newIndex = selectorLength - 1;
|
||||
}
|
||||
selector.selectedIndex = newIndex;
|
||||
changeFont();
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateParagraph() {
|
||||
// update paragraph text based on user input:
|
||||
const txt = document.getElementById('textInput');
|
||||
const paragraphs = document.getElementsByClassName('sampletext');
|
||||
for (i = 0; i < paragraphs.length; i++) {
|
||||
paragraph = paragraphs[i];
|
||||
paragraph.textContent = txt.value;
|
||||
}
|
||||
|
||||
// update other elements:
|
||||
document.getElementById('metricsLine').textContent = txt.value;
|
||||
}
|
||||
function updateFeatures() {
|
||||
// update features based on user input:
|
||||
// first, get feature on/off line:
|
||||
var cssCode = "";
|
||||
var codeLine = "";
|
||||
var checkboxes = document.getElementsByClassName("otFeature")
|
||||
for (i = 0; i < checkboxes.length; i++) {
|
||||
var checkbox = checkboxes[i];
|
||||
codeLine += '"'+checkbox.id+'" ';
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
if (checkbox.name=="kern") {
|
||||
cssCode += "font-kerning: "
|
||||
cssCode += checkbox.checked ? 'normal; ' : 'none; ';
|
||||
} else if (checkbox.name=="liga") {
|
||||
codeLine += '"clig" '
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'common-ligatures contextual; ' : 'no-common-ligatures no-contextual; ';
|
||||
} else if (checkbox.name=="dlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'discretionary-ligatures; ' : 'no-discretionary-ligatures; ';
|
||||
} else if (checkbox.name=="hlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'historical-ligatures; ' : 'no-historical-ligatures; ';
|
||||
}
|
||||
}
|
||||
codeLine = codeLine.slice(0, -2)
|
||||
|
||||
// then, apply line for every browser:
|
||||
const prefixes = ["","-moz-","-webkit-","-ms-","-o-",];
|
||||
const suffix = "font-feature-settings: "
|
||||
for (i = 0; i < prefixes.length; i++) {
|
||||
var prefix = prefixes[i];
|
||||
cssCode += prefix
|
||||
cssCode += suffix
|
||||
cssCode += codeLine
|
||||
cssCode += "; "
|
||||
}
|
||||
|
||||
document.getElementById('waterfall').style.cssText = cssCode;
|
||||
document.getElementById('featureLine').innerHTML = cssCode.replace(/;/g,";<br/>");
|
||||
changeFont();
|
||||
}
|
||||
function changeFont() {
|
||||
var selected_index = selector.selectedIndex;
|
||||
var selected_option_text = selector.options[selected_index].text;
|
||||
document.getElementById('waterfall').style.fontFamily = selected_option_text;
|
||||
}
|
||||
function setDefaultText(defaultText) {
|
||||
document.getElementById('textInput').value = decodeEntities(defaultText);
|
||||
updateParagraph();
|
||||
}
|
||||
function setLat1() {
|
||||
const lat1 = " 1234567890 /ي ك / ایران همیشه جاویدان";
|
||||
return setDefaultText(lat1);
|
||||
}
|
||||
function setCharset() {
|
||||
const completeCharSet = ' 1234567890 /ي ك / ایران همیشه جاویدان';
|
||||
setDefaultText(completeCharSet);
|
||||
}
|
||||
function decodeEntities(string){
|
||||
var elem = document.createElement('div');
|
||||
elem.innerHTML = string;
|
||||
return elem.textContent;
|
||||
}
|
||||
function vanish(item) {
|
||||
item.style.setProperty("display", "none");
|
||||
}
|
||||
function toggleLeftRight() {
|
||||
const waterfall = document.getElementById("waterfall");
|
||||
if (waterfall.dir != "rtl") {
|
||||
waterfall.dir = "rtl";
|
||||
waterfall.align = "right";
|
||||
} else {
|
||||
waterfall.dir = "";
|
||||
waterfall.align = "";
|
||||
}
|
||||
}
|
||||
function toggleInverse() {
|
||||
const testText = document.getElementById("waterfall");
|
||||
if (testText) {
|
||||
const link = document.getElementById("invert");
|
||||
if (testText.className == "●") {
|
||||
testText.className = "○";
|
||||
link.textContent = "🔳";
|
||||
} else {
|
||||
testText.className = "●";
|
||||
link.textContent = "🔲";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,418 @@
|
||||
<head>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9" />
|
||||
<title>Melli</title>
|
||||
<style type="text/css" media="screen">
|
||||
@font-face { font-family: 'WOFF Melli-Regular'; src: url('Melli-Regular.woff'); }
|
||||
@font-face { font-family: 'WOFF2 Melli-Regular'; src: url('Melli-Regular.woff2'); }
|
||||
|
||||
body {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
.features, .label, a, #controls {
|
||||
font: normal normal normal small sans-serif;
|
||||
}
|
||||
.features .emojiButton {
|
||||
vertical-align: -5%;
|
||||
font-size: small;
|
||||
}
|
||||
.emojiButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
#flexbox {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
}
|
||||
#controls {
|
||||
flex: 0 1 auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
border: 0px solid transparent;
|
||||
height: auto;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
#metricsLine {
|
||||
background-color: #EEE;
|
||||
border-top: 1px solid #AAA;
|
||||
border-bottom: 1px solid #AAA;
|
||||
width: 100%;
|
||||
margin: 0.2em 0;
|
||||
padding: 0 0;
|
||||
font-size: 2em;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
text-overflow: none;
|
||||
display: none;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
}
|
||||
#metricsLine::-webkit-scrollbar { /* WebKit */
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
#waterfall {
|
||||
flex: 1 1 auto;
|
||||
border: 0 solid transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
color: black;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
font-family: "WOFF Melli-Regular";
|
||||
font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-moz-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-webkit-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-ms-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-o-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
}
|
||||
div, p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#waterfall p {
|
||||
margin-bottom: 0.8em;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.○ .sampletext {
|
||||
-webkit-text-stroke: 1px black;
|
||||
-webkit-text-fill-color: #FFF0;
|
||||
}
|
||||
.features, .label, a {
|
||||
color: #888;
|
||||
}
|
||||
.label {
|
||||
background-color: #ddd;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
span#p08 { font-size: 08pt; padding: 08pt 0; }
|
||||
span#p09 { font-size: 09pt; padding: 09pt 0; }
|
||||
span#p10 { font-size: 10pt; padding: 10pt 0; }
|
||||
span#p11 { font-size: 11pt; padding: 11pt 0; }
|
||||
span#p12 { font-size: 12pt; padding: 12pt 0; }
|
||||
span#p13 { font-size: 13pt; padding: 13pt 0; }
|
||||
span#p14 { font-size: 14pt; padding: 14pt 0; }
|
||||
span#p15 { font-size: 15pt; padding: 15pt 0; }
|
||||
span#p16 { font-size: 16pt; padding: 16pt 0; }
|
||||
span#largeParagraph { font-size: 32pt; padding: 32pt 0; }
|
||||
span#veryLargeParagraph { font-size: 100pt; padding: 100pt 0; }
|
||||
|
||||
.otFeatureLabel {
|
||||
color: #666;
|
||||
background-color: #ddd;
|
||||
padding: 0.2em 0.5em 0.3em 0.5em;
|
||||
margin: 0 .04em;
|
||||
line-height: 2em;
|
||||
border-radius: 0.3em;
|
||||
border: 0;
|
||||
text-align:center;
|
||||
}
|
||||
.otFeatureLabel, .otFeature {
|
||||
position: relative;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.otFeatureLabel {
|
||||
padding: 0.2em 0.5em 0.3em 0.5em;
|
||||
margin: 0 .04em;
|
||||
line-height: 2em;
|
||||
color: #666;
|
||||
background-color: #ddd;
|
||||
border-radius: 0.3em;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
z-index: 6;
|
||||
}
|
||||
.wrapper {
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
select {
|
||||
float: left;
|
||||
margin: 0 0.5em 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
input[type=text] {
|
||||
border: 1px solid #999;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.features {
|
||||
clear: left;
|
||||
}
|
||||
input[type=checkbox]:checked + label {
|
||||
visibility: visible;
|
||||
color: #fff;
|
||||
background-color: #888;
|
||||
}
|
||||
.otFeature {
|
||||
visibility: collapse;
|
||||
margin: 0 -1em 0 0;
|
||||
}
|
||||
.otFeatureLabel .tooltip {
|
||||
visibility: hidden;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 0px 5px;
|
||||
top: -2em;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
}
|
||||
.otFeatureLabel:hover .tooltip {
|
||||
visibility: visible;
|
||||
}
|
||||
#featureLine {
|
||||
display: none;
|
||||
border-bottom: 1px solid #999;
|
||||
padding: 0.5em 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
/* Footer paragraph: */
|
||||
#helptext {
|
||||
color: black;
|
||||
background-color: #ddd;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding: 2px
|
||||
width: 100%;
|
||||
font: x-small sans-serif;
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #333;
|
||||
}
|
||||
.features, .label, a, body, p, #metricsLine {
|
||||
color: white;
|
||||
}
|
||||
.label {
|
||||
background-color: black;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
.otFeatureLabel, input[type=text] {
|
||||
color: white;
|
||||
background-color: black;
|
||||
}
|
||||
input[type=checkbox]:checked + label {
|
||||
color: black;
|
||||
background-color: #aaa;
|
||||
}
|
||||
#helptext {
|
||||
background-color: #777;
|
||||
}
|
||||
.○ .sampletext {
|
||||
-webkit-text-stroke: 1px white;
|
||||
-webkit-text-fill-color: #0000;
|
||||
}
|
||||
#metricsLine {
|
||||
background-color: #222;
|
||||
border-color: #777;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="document.getElementById('textInput').focus();setCharset();">
|
||||
<div id="flexbox">
|
||||
<div id="controls">
|
||||
<div>
|
||||
<select size="1" id="fontFamilySelector" name="fontFamilySelector" onchange="changeFont()">
|
||||
<option value="Melli-Regular.woff">WOFF Melli-Regular</option>
|
||||
<option value="Melli-Regular.woff2">WOFF2 Melli-Regular</option>
|
||||
</select>
|
||||
<div class="wrapper" spellcheck="false">
|
||||
<input type="text" value="Type Text Here." id="textInput" onclick="this.select();" onkeyup="updateParagraph()" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="features">
|
||||
<a href="javascript:setCharset();">Charset</a>
|
||||
<a href="javascript:setLat1();">Lat1</a>
|
||||
 
|
||||
<a href="https://caniuse.com/#feat=woff">woff</a>
|
||||
<a href="https://caniuse.com/#feat=woff2">woff2</a>
|
||||
 
|
||||
<a onclick="toggleInverse();" id="invert" class="emojiButton">🔲</a>
|
||||
<label><input type="checkbox" id="kern" value="kern" class="otFeature" onchange="updateFeatures()" checked><label for="kern" class="otFeatureLabel">kern</label>
|
||||
<label><input type="checkbox" id="liga" value="liga" class="otFeature" onchange="updateFeatures()" checked><label for="liga" class="otFeatureLabel">liga/clig</label>
|
||||
<label><input type="checkbox" id="calt" value="calt" class="otFeature" onchange="updateFeatures()" checked><label for="calt" class="otFeatureLabel">calt</label>
|
||||
<input type="checkbox" id="init" value="init" class="otFeature" onchange="updateFeatures()"><label for="init" class="otFeatureLabel">init</label>
|
||||
<input type="checkbox" id="medi" value="medi" class="otFeature" onchange="updateFeatures()"><label for="medi" class="otFeatureLabel">medi</label>
|
||||
<input type="checkbox" id="fina" value="fina" class="otFeature" onchange="updateFeatures()"><label for="fina" class="otFeatureLabel">fina</label>
|
||||
<input type="checkbox" id="rlig" value="rlig" class="otFeature" onchange="updateFeatures()"><label for="rlig" class="otFeatureLabel">rlig</label>
|
||||
<input type="checkbox" id="dlig" value="dlig" class="otFeature" onchange="updateFeatures()"><label for="dlig" class="otFeatureLabel">dlig</label>
|
||||
<input type="checkbox" id="ss01" value="ss01" class="otFeature" onchange="updateFeatures()"><label for="ss01" class="otFeatureLabel">ss01</label>
|
||||
<input type="checkbox" id="ss02" value="ss02" class="otFeature" onchange="updateFeatures()"><label for="ss02" class="otFeatureLabel">ss02</label>
|
||||
<input type="checkbox" id="ss03" value="ss03" class="otFeature" onchange="updateFeatures()"><label for="ss03" class="otFeatureLabel">ss03</label>
|
||||
<input type="checkbox" id="ss04" value="ss04" class="otFeature" onchange="updateFeatures()"><label for="ss04" class="otFeatureLabel">ss04</label>
|
||||
<label><input type="checkbox" value="show" onchange="updateFeatures();document.getElementById('featureLine').style.display=this.checked?'block':'none'">CSS</label>
|
||||
<label><input type="checkbox" value="show" onchange="updateFeatures();document.getElementById('metricsLine').style.display=this.checked?'block':'none'">Metrics</label>
|
||||
</p>
|
||||
<p class="features" id="featureLine">font-feature-settings: "kern" on, "liga" on, "calt" on;</p>
|
||||
</div>
|
||||
<div id="waterfall" class="●">
|
||||
<div id="metricsLine"></div>
|
||||
<p><span class="label">08</span> <span class="sampletext" id="p08"></span></p>
|
||||
<p><span class="label">09</span> <span class="sampletext" id="p09"></span></p>
|
||||
<p><span class="label">10</span> <span class="sampletext" id="p10"></span></p>
|
||||
<p><span class="label">11</span> <span class="sampletext" id="p11"></span></p>
|
||||
<p><span class="label">12</span> <span class="sampletext" id="p12"></span></p>
|
||||
<p><span class="label">13</span> <span class="sampletext" id="p13"></span></p>
|
||||
<p><span class="label">14</span> <span class="sampletext" id="p14"></span></p>
|
||||
<p><span class="label">15</span> <span class="sampletext" id="p15"></span></p>
|
||||
<p><span class="label">16</span> <span class="sampletext" id="p16"></span></p>
|
||||
<p><span class="sampletext" id="largeParagraph"></span></p>
|
||||
<p><span class="sampletext" id="veryLargeParagraph"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Disclaimer -->
|
||||
<p id="helptext" onmouseleave="vanish(this);">
|
||||
Ctrl-R: Reset Charset. Ctrl-L: Latin1. Ctrl-J: LTR/RTL. Ctrl-comma/period: step through fonts. Pull mouse across this note to make it disappear.
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
const selector = document.getElementById("fontFamilySelector");
|
||||
const selectorOptions = selector.options;
|
||||
const selectorLength = selectorOptions.length;
|
||||
|
||||
document.addEventListener('keyup', keyAnalysis);
|
||||
|
||||
function keyAnalysis(event) {
|
||||
if (event.ctrlKey) {
|
||||
if (event.code == 'KeyR') {
|
||||
setCharset();
|
||||
} else if (event.code == 'KeyL') {
|
||||
setLat1();
|
||||
} else if (event.code == 'KeyJ') {
|
||||
toggleLeftRight();
|
||||
} else if (event.code == 'Period') {
|
||||
selector.selectedIndex = (selector.selectedIndex + 1) % selectorLength;
|
||||
changeFont();
|
||||
} else if (event.code == 'Comma') {
|
||||
var newIndex = selector.selectedIndex - 1;
|
||||
if (newIndex<0) {
|
||||
newIndex = selectorLength - 1;
|
||||
}
|
||||
selector.selectedIndex = newIndex;
|
||||
changeFont();
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateParagraph() {
|
||||
// update paragraph text based on user input:
|
||||
const txt = document.getElementById('textInput');
|
||||
const paragraphs = document.getElementsByClassName('sampletext');
|
||||
for (i = 0; i < paragraphs.length; i++) {
|
||||
paragraph = paragraphs[i];
|
||||
paragraph.textContent = txt.value;
|
||||
}
|
||||
|
||||
// update other elements:
|
||||
document.getElementById('metricsLine').textContent = txt.value;
|
||||
}
|
||||
function updateFeatures() {
|
||||
// update features based on user input:
|
||||
// first, get feature on/off line:
|
||||
var cssCode = "";
|
||||
var codeLine = "";
|
||||
var checkboxes = document.getElementsByClassName("otFeature")
|
||||
for (i = 0; i < checkboxes.length; i++) {
|
||||
var checkbox = checkboxes[i];
|
||||
codeLine += '"'+checkbox.id+'" ';
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
if (checkbox.name=="kern") {
|
||||
cssCode += "font-kerning: "
|
||||
cssCode += checkbox.checked ? 'normal; ' : 'none; ';
|
||||
} else if (checkbox.name=="liga") {
|
||||
codeLine += '"clig" '
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'common-ligatures contextual; ' : 'no-common-ligatures no-contextual; ';
|
||||
} else if (checkbox.name=="dlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'discretionary-ligatures; ' : 'no-discretionary-ligatures; ';
|
||||
} else if (checkbox.name=="hlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'historical-ligatures; ' : 'no-historical-ligatures; ';
|
||||
}
|
||||
}
|
||||
codeLine = codeLine.slice(0, -2)
|
||||
|
||||
// then, apply line for every browser:
|
||||
const prefixes = ["","-moz-","-webkit-","-ms-","-o-",];
|
||||
const suffix = "font-feature-settings: "
|
||||
for (i = 0; i < prefixes.length; i++) {
|
||||
var prefix = prefixes[i];
|
||||
cssCode += prefix
|
||||
cssCode += suffix
|
||||
cssCode += codeLine
|
||||
cssCode += "; "
|
||||
}
|
||||
|
||||
document.getElementById('waterfall').style.cssText = cssCode;
|
||||
document.getElementById('featureLine').innerHTML = cssCode.replace(/;/g,";<br/>");
|
||||
changeFont();
|
||||
}
|
||||
function changeFont() {
|
||||
var selected_index = selector.selectedIndex;
|
||||
var selected_option_text = selector.options[selected_index].text;
|
||||
document.getElementById('waterfall').style.fontFamily = selected_option_text;
|
||||
}
|
||||
function setDefaultText(defaultText) {
|
||||
document.getElementById('textInput').value = decodeEntities(defaultText);
|
||||
updateParagraph();
|
||||
}
|
||||
function setLat1() {
|
||||
const lat1 = " ایران همیشه جاویدان";
|
||||
return setDefaultText(lat1);
|
||||
}
|
||||
function setCharset() {
|
||||
const completeCharSet = ' ایران همیشه جاویدان';
|
||||
setDefaultText(completeCharSet);
|
||||
}
|
||||
function decodeEntities(string){
|
||||
var elem = document.createElement('div');
|
||||
elem.innerHTML = string;
|
||||
return elem.textContent;
|
||||
}
|
||||
function vanish(item) {
|
||||
item.style.setProperty("display", "none");
|
||||
}
|
||||
function toggleLeftRight() {
|
||||
const waterfall = document.getElementById("waterfall");
|
||||
if (waterfall.dir != "rtl") {
|
||||
waterfall.dir = "rtl";
|
||||
waterfall.align = "right";
|
||||
} else {
|
||||
waterfall.dir = "";
|
||||
waterfall.align = "";
|
||||
}
|
||||
}
|
||||
function toggleInverse() {
|
||||
const testText = document.getElementById("waterfall");
|
||||
if (testText) {
|
||||
const link = document.getElementById("invert");
|
||||
if (testText.className == "●") {
|
||||
testText.className = "○";
|
||||
link.textContent = "🔳";
|
||||
} else {
|
||||
testText.className = "●";
|
||||
link.textContent = "🔲";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
@@ -0,0 +1,526 @@
|
||||
<head>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9" />
|
||||
<title>Melli</title>
|
||||
<style type="text/css" media="screen">
|
||||
@font-face {
|
||||
font-family: 'TTF Melli';
|
||||
src: url('Melli.ttf');
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.features,
|
||||
.label,
|
||||
a,
|
||||
#controls {
|
||||
font: normal normal normal small sans-serif;
|
||||
}
|
||||
|
||||
.features .emojiButton {
|
||||
vertical-align: -5%;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.emojiButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#flexbox {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#controls {
|
||||
flex: 0 1 auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
border: 0px solid transparent;
|
||||
height: auto;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
#metricsLine {
|
||||
background-color: #EEE;
|
||||
border-top: 1px solid #AAA;
|
||||
border-bottom: 1px solid #AAA;
|
||||
width: 100%;
|
||||
margin: 0.2em 0;
|
||||
padding: 0 0;
|
||||
font-size: 2em;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
text-overflow: none;
|
||||
display: none;
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
-ms-overflow-style: none;
|
||||
/* Internet Explorer 10+ */
|
||||
}
|
||||
|
||||
#metricsLine::-webkit-scrollbar {
|
||||
/* WebKit */
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
#waterfall {
|
||||
flex: 1 1 auto;
|
||||
border: 0 solid transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
color: black;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
font-family: "TTF Melli";
|
||||
font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-moz-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-webkit-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-ms-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
-o-font-feature-settings: "kern" on, "liga" on, "calt" on;
|
||||
}
|
||||
|
||||
div,
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#waterfall p {
|
||||
margin-bottom: 0.8em;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.○ .sampletext {
|
||||
-webkit-text-stroke: 1px black;
|
||||
-webkit-text-fill-color: #FFF0;
|
||||
}
|
||||
|
||||
.features,
|
||||
.label,
|
||||
a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.label {
|
||||
background-color: #ddd;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
span#p08 {
|
||||
font-size: 08pt;
|
||||
padding: 08pt 0;
|
||||
}
|
||||
|
||||
span#p09 {
|
||||
font-size: 09pt;
|
||||
padding: 09pt 0;
|
||||
}
|
||||
|
||||
span#p10 {
|
||||
font-size: 10pt;
|
||||
padding: 10pt 0;
|
||||
}
|
||||
|
||||
span#p11 {
|
||||
font-size: 11pt;
|
||||
padding: 11pt 0;
|
||||
}
|
||||
|
||||
span#p12 {
|
||||
font-size: 12pt;
|
||||
padding: 12pt 0;
|
||||
}
|
||||
|
||||
span#p13 {
|
||||
font-size: 13pt;
|
||||
padding: 13pt 0;
|
||||
}
|
||||
|
||||
span#p14 {
|
||||
font-size: 14pt;
|
||||
padding: 14pt 0;
|
||||
}
|
||||
|
||||
span#p15 {
|
||||
font-size: 15pt;
|
||||
padding: 15pt 0;
|
||||
}
|
||||
|
||||
span#p16 {
|
||||
font-size: 16pt;
|
||||
padding: 16pt 0;
|
||||
}
|
||||
|
||||
span#largeParagraph {
|
||||
font-size: 32pt;
|
||||
padding: 32pt 0;
|
||||
}
|
||||
|
||||
span#veryLargeParagraph {
|
||||
font-size: 100pt;
|
||||
padding: 100pt 0;
|
||||
}
|
||||
|
||||
.otFeatureLabel {
|
||||
color: #666;
|
||||
background-color: #ddd;
|
||||
padding: 0.2em 0.5em 0.3em 0.5em;
|
||||
margin: 0 .04em;
|
||||
line-height: 2em;
|
||||
border-radius: 0.3em;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.otFeatureLabel,
|
||||
.otFeature {
|
||||
position: relative;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.otFeatureLabel {
|
||||
padding: 0.2em 0.5em 0.3em 0.5em;
|
||||
margin: 0 .04em;
|
||||
line-height: 2em;
|
||||
color: #666;
|
||||
background-color: #ddd;
|
||||
border-radius: 0.3em;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
|
||||
select {
|
||||
float: left;
|
||||
margin: 0 0.5em 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
border: 1px solid #999;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.features {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
input[type=checkbox]:checked+label {
|
||||
visibility: visible;
|
||||
color: #fff;
|
||||
background-color: #888;
|
||||
}
|
||||
|
||||
.otFeature {
|
||||
visibility: collapse;
|
||||
margin: 0 -1em 0 0;
|
||||
}
|
||||
|
||||
.otFeatureLabel .tooltip {
|
||||
visibility: hidden;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 0px 5px;
|
||||
top: -2em;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.otFeatureLabel:hover .tooltip {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#featureLine {
|
||||
display: none;
|
||||
border-bottom: 1px solid #999;
|
||||
padding: 0.5em 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
/* Footer paragraph: */
|
||||
#helptext {
|
||||
color: black;
|
||||
background-color: #ddd;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding: 2px width: 100%;
|
||||
font: x-small sans-serif;
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.features,
|
||||
.label,
|
||||
a,
|
||||
body,
|
||||
p,
|
||||
#metricsLine {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.label {
|
||||
background-color: black;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
.otFeatureLabel,
|
||||
input[type=text] {
|
||||
color: white;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
input[type=checkbox]:checked+label {
|
||||
color: black;
|
||||
background-color: #aaa;
|
||||
}
|
||||
|
||||
#helptext {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
.○ .sampletext {
|
||||
-webkit-text-stroke: 1px white;
|
||||
-webkit-text-fill-color: #0000;
|
||||
}
|
||||
|
||||
#metricsLine {
|
||||
background-color: #222;
|
||||
border-color: #777;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body onload="document.getElementById('textInput').focus();setCharset();">
|
||||
<div id="flexbox">
|
||||
<div id="controls">
|
||||
<div>
|
||||
<select size="1" id="fontFamilySelector" name="fontFamilySelector" onchange="changeFont()">
|
||||
<option value="Melli.ttf">TTF Melli</option>
|
||||
</select>
|
||||
<div class="wrapper" spellcheck="false">
|
||||
<input type="text" value="Type Text Here." id="textInput" onclick="this.select();"
|
||||
onkeyup="updateParagraph()" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="features">
|
||||
<a href="javascript:setCharset();">Charset</a>
|
||||
<a href="javascript:setLat1();">Lat1</a>
|
||||
 
|
||||
<a href="https://caniuse.com/#feat=woff">woff</a>
|
||||
<a href="https://caniuse.com/#feat=woff2">woff2</a>
|
||||
 
|
||||
<a onclick="toggleInverse();" id="invert" class="emojiButton">🔲</a>
|
||||
<label><input type="checkbox" id="kern" value="kern" class="otFeature" onchange="updateFeatures()"
|
||||
checked><label for="kern" class="otFeatureLabel">kern</label>
|
||||
<label><input type="checkbox" id="liga" value="liga" class="otFeature" onchange="updateFeatures()"
|
||||
checked><label for="liga" class="otFeatureLabel">liga/clig</label>
|
||||
<label><input type="checkbox" id="calt" value="calt" class="otFeature"
|
||||
onchange="updateFeatures()" checked><label for="calt"
|
||||
class="otFeatureLabel">calt</label>
|
||||
<input type="checkbox" id="init" value="init" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="init" class="otFeatureLabel">init</label>
|
||||
<input type="checkbox" id="medi" value="medi" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="medi" class="otFeatureLabel">medi</label>
|
||||
<input type="checkbox" id="fina" value="fina" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="fina" class="otFeatureLabel">fina</label>
|
||||
<input type="checkbox" id="rlig" value="rlig" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="rlig" class="otFeatureLabel">rlig</label>
|
||||
<input type="checkbox" id="dlig" value="dlig" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="dlig" class="otFeatureLabel">dlig</label>
|
||||
<input type="checkbox" id="ss01" value="ss01" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="ss01" class="otFeatureLabel">ss01</label>
|
||||
<input type="checkbox" id="ss02" value="ss02" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="ss02" class="otFeatureLabel">ss02</label>
|
||||
<input type="checkbox" id="ss03" value="ss03" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="ss03" class="otFeatureLabel">ss03</label>
|
||||
<input type="checkbox" id="ss04" value="ss04" class="otFeature"
|
||||
onchange="updateFeatures()"><label for="ss04" class="otFeatureLabel">ss04</label>
|
||||
<label><input type="checkbox" value="show"
|
||||
onchange="updateFeatures();document.getElementById('featureLine').style.display=this.checked?'block':'none'">CSS</label>
|
||||
<label><input type="checkbox" value="show"
|
||||
onchange="updateFeatures();document.getElementById('metricsLine').style.display=this.checked?'block':'none'">Metrics</label>
|
||||
</p>
|
||||
<p class="features" id="featureLine">font-feature-settings: "kern" on, "liga" on, "calt" on;</p>
|
||||
</div>
|
||||
<div id="waterfall" class="●">
|
||||
<div id="metricsLine"></div>
|
||||
<p><span class="label">08</span> <span class="sampletext" id="p08"></span></p>
|
||||
<p><span class="label">09</span> <span class="sampletext" id="p09"></span></p>
|
||||
<p><span class="label">10</span> <span class="sampletext" id="p10"></span></p>
|
||||
<p><span class="label">11</span> <span class="sampletext" id="p11"></span></p>
|
||||
<p><span class="label">12</span> <span class="sampletext" id="p12"></span></p>
|
||||
<p><span class="label">13</span> <span class="sampletext" id="p13"></span></p>
|
||||
<p><span class="label">14</span> <span class="sampletext" id="p14"></span></p>
|
||||
<p><span class="label">15</span> <span class="sampletext" id="p15"></span></p>
|
||||
<p><span class="label">16</span> <span class="sampletext" id="p16"></span></p>
|
||||
<p><span class="sampletext" id="largeParagraph"></span></p>
|
||||
<p><span class="sampletext" id="veryLargeParagraph"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Disclaimer -->
|
||||
<p id="helptext" onmouseleave="vanish(this);">
|
||||
Ctrl-R: Reset Charset. Ctrl-L: Latin1. Ctrl-J: LTR/RTL. Ctrl-comma/period: step through fonts. Pull mouse across
|
||||
this note to make it disappear.
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
const selector = document.getElementById("fontFamilySelector");
|
||||
const selectorOptions = selector.options;
|
||||
const selectorLength = selectorOptions.length;
|
||||
|
||||
document.addEventListener('keyup', keyAnalysis);
|
||||
|
||||
function keyAnalysis(event) {
|
||||
if (event.ctrlKey) {
|
||||
if (event.code == 'KeyR') {
|
||||
setCharset();
|
||||
} else if (event.code == 'KeyL') {
|
||||
setLat1();
|
||||
} else if (event.code == 'KeyJ') {
|
||||
toggleLeftRight();
|
||||
} else if (event.code == 'Period') {
|
||||
selector.selectedIndex = (selector.selectedIndex + 1) % selectorLength;
|
||||
changeFont();
|
||||
} else if (event.code == 'Comma') {
|
||||
var newIndex = selector.selectedIndex - 1;
|
||||
if (newIndex < 0) {
|
||||
newIndex = selectorLength - 1;
|
||||
}
|
||||
selector.selectedIndex = newIndex;
|
||||
changeFont();
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateParagraph() {
|
||||
// update paragraph text based on user input:
|
||||
const txt = document.getElementById('textInput');
|
||||
const paragraphs = document.getElementsByClassName('sampletext');
|
||||
for (i = 0; i < paragraphs.length; i++) {
|
||||
paragraph = paragraphs[i];
|
||||
paragraph.textContent = txt.value;
|
||||
}
|
||||
|
||||
// update other elements:
|
||||
document.getElementById('metricsLine').textContent = txt.value;
|
||||
}
|
||||
function updateFeatures() {
|
||||
// update features based on user input:
|
||||
// first, get feature on/off line:
|
||||
var cssCode = "";
|
||||
var codeLine = "";
|
||||
var checkboxes = document.getElementsByClassName("otFeature")
|
||||
for (i = 0; i < checkboxes.length; i++) {
|
||||
var checkbox = checkboxes[i];
|
||||
codeLine += '"' + checkbox.id + '" ';
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
if (checkbox.name == "kern") {
|
||||
cssCode += "font-kerning: "
|
||||
cssCode += checkbox.checked ? 'normal; ' : 'none; ';
|
||||
} else if (checkbox.name == "liga") {
|
||||
codeLine += '"clig" '
|
||||
codeLine += checkbox.checked ? 'on, ' : 'off, ';
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'common-ligatures contextual; ' : 'no-common-ligatures no-contextual; ';
|
||||
} else if (checkbox.name == "dlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'discretionary-ligatures; ' : 'no-discretionary-ligatures; ';
|
||||
} else if (checkbox.name == "hlig") {
|
||||
cssCode += "font-variant-ligatures: "
|
||||
cssCode += checkbox.checked ? 'historical-ligatures; ' : 'no-historical-ligatures; ';
|
||||
}
|
||||
}
|
||||
codeLine = codeLine.slice(0, -2)
|
||||
|
||||
// then, apply line for every browser:
|
||||
const prefixes = ["", "-moz-", "-webkit-", "-ms-", "-o-",];
|
||||
const suffix = "font-feature-settings: "
|
||||
for (i = 0; i < prefixes.length; i++) {
|
||||
var prefix = prefixes[i];
|
||||
cssCode += prefix
|
||||
cssCode += suffix
|
||||
cssCode += codeLine
|
||||
cssCode += "; "
|
||||
}
|
||||
|
||||
document.getElementById('waterfall').style.cssText = cssCode;
|
||||
document.getElementById('featureLine').innerHTML = cssCode.replace(/;/g, ";<br/>");
|
||||
changeFont();
|
||||
}
|
||||
function changeFont() {
|
||||
var selected_index = selector.selectedIndex;
|
||||
var selected_option_text = selector.options[selected_index].text;
|
||||
document.getElementById('waterfall').style.fontFamily = selected_option_text;
|
||||
}
|
||||
function setDefaultText(defaultText) {
|
||||
document.getElementById('textInput').value = decodeEntities(defaultText);
|
||||
updateParagraph();
|
||||
}
|
||||
function setLat1() {
|
||||
const lat1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØŒÞÙÚÛÜÝŸ àáâãäåæçèéêëìíîïðñòóôõöøœþßùúûüýÿ .,:;·…¿?¡!«»‹› /|¦\()[]{}_-–—‚„‘’“”"' #&§@•­*†‡¶ +×÷±=<>¬μ ^~´`ˆ¯˜¨¸ ¥€£$¢¤ƒ ™®© 1234567890 ªº°%‰ ¹²³¼½¾";
|
||||
return setDefaultText(lat1);
|
||||
}
|
||||
function setCharset() {
|
||||
const completeCharSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzءاأﺄإﺈآﺂٱﭑٮبﺐﺒﺑپﭗﭙﭘتﺖﺘﺗثﺚﺜﺛٹﭧﭩﭨجﺞﺠﺟچﭻﭽﭼحﺢخﺦﺨﺧدذﺬڈرزﺰڑﮍڕژﮋسﺲﺳشﺶﺸﺷصﺺﺼﺻضﺾﻀﺿطﻂﻄﻃظﻆﻈﻇعﻊﻌﻋغﻎﻐﻏفﻒﻔﻓڤﭫﭭﭬڡٯقﻖﻘﻗكﻚﻜﻛکﮏﮑﮐگﮓﮕﮔڪلﻠﻟڵمﻢنﻦﻨﻧںهﻪﻬۀﮥہﮧﮩﮨۂھﮫﮭﮬةﺔۃوؤﺆۆﯚۇﯘۉﯣىﻰيﻲﻴﻳئﺊﺌﺋێیﯽﯿﯾےﮯۓﮱەـﻻﻼﻷﻸﻹﻺﻵﻶ٫٬٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹0123456789    ​
۔،؛؟٭﴾﴿.,:;…!?•*#/\-_(){}[]„“”‘’«»‹›"'﷼٪@&©®™°′″|¦$+−×÷=%∕ ‌ ‍ ‎ ‏ ؕ ﮲ ﮳ ﮴ ﮵ ﮹ ﮶ ٰ ٖ ٔ ٕ ً ٌ ٍ َ ُ ِ ّ ْ ٓ ٘ ٚ ٛ';
|
||||
setDefaultText(completeCharSet);
|
||||
}
|
||||
function decodeEntities(string) {
|
||||
var elem = document.createElement('div');
|
||||
elem.innerHTML = string;
|
||||
return elem.textContent;
|
||||
}
|
||||
function vanish(item) {
|
||||
item.style.setProperty("display", "none");
|
||||
}
|
||||
function toggleLeftRight() {
|
||||
const waterfall = document.getElementById("waterfall");
|
||||
if (waterfall.dir != "rtl") {
|
||||
waterfall.dir = "rtl";
|
||||
waterfall.align = "right";
|
||||
} else {
|
||||
waterfall.dir = "";
|
||||
waterfall.align = "";
|
||||
}
|
||||
}
|
||||
function toggleInverse() {
|
||||
const testText = document.getElementById("waterfall");
|
||||
if (testText) {
|
||||
const link = document.getElementById("invert");
|
||||
if (testText.className == "●") {
|
||||
testText.className = "○";
|
||||
link.textContent = "🔳";
|
||||
} else {
|
||||
testText.className = "●";
|
||||
link.textContent = "🔲";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user