UI优化完善初版

This commit is contained in:
lt
2026-05-01 06:48:21 +08:00
parent e1ba31afda
commit cfb60d7aae
12228 changed files with 1358947 additions and 2593 deletions
+8 -6
View File
@@ -1,14 +1,18 @@
from fastapi import APIRouter, HTTPException, Query
from fastapi import APIRouter, HTTPException, Query, Depends
from pathlib import Path
import os
from datetime import datetime, date
from ..database import get_db
from ..models import User
from .auth import get_current_user
router = APIRouter(prefix="/api/articles", tags=["articles"])
PROJECT_ROOT = Path('/root/openclaw-workspace/projects/yu-zhi-ran')
@router.get("/drafts")
def list_drafts(publish_date: str = None):
def list_drafts(publish_date: str = None, current_user: User = Depends(get_current_user)):
"""列出指定日期的草稿文件(三平台)"""
if not publish_date:
publish_date = date.today().isoformat()
@@ -28,21 +32,19 @@ def list_drafts(publish_date: str = None):
return {"date": publish_date, "files": result}
@router.get("/{topic_id}/preview")
def preview_article(topic_id: str, platform: str = "zhihu", publish_date: str = None):
def preview_article(topic_id: str, platform: str = "zhihu", publish_date: str = None, current_user: User = Depends(get_current_user)):
"""预览某选题的HTML内容"""
if not publish_date:
publish_date = date.today().isoformat()
filename = f"{platform}_{topic_id}_{platform}.html"
file_path = PROJECT_ROOT / "automation" / "data" / "releases" / publish_date / platform / filename
# DEBUG
print(f"[DEBUG] file_path={file_path}, exists={file_path.exists()}")
if not file_path.exists():
raise HTTPException(status_code=404, detail=f"Article not found: {file_path}")
content = file_path.read_text(encoding='utf-8')
return {"topic_id": topic_id, "platform": platform, "html": content}
@router.get("/optimization-report")
def get_optimization_report(publish_date: str = None):
def get_optimization_report(publish_date: str = None, current_user: User = Depends(get_current_user)):
"""获取合规优化报告"""
if not publish_date:
publish_date = date.today().isoformat()
+6
View File
@@ -146,6 +146,12 @@ def get_current_user(request: Request, db: Session = Depends(get_db)) -> User:
token = auth_header.split(" ")[1]
user = verify_token(token, db)
return user
def get_current_admin(current_user: User = Depends(get_current_user)) -> User:
"""依赖项:验证管理员权限"""
if current_user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return current_user
@router.get("/me")
def get_me(
request: Request,
@@ -0,0 +1,71 @@
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from pathlib import Path
import subprocess
from ..database import get_db
from ..models import User
from .auth import get_current_admin
router = APIRouter(prefix="/api", tags=["optimizer_logs"])
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent.parent
@router.post("/optimizer/run")
def run_optimizer(
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin)
):
"""
触发合规优化器运行(管理员)
"""
try:
body = request.json()
except Exception:
raise HTTPException(status_code=400, detail="Invalid JSON")
topic_id = body.get("topic_id")
if not topic_id:
raise HTTPException(status_code=400, detail="topic_id is required")
script_path = PROJECT_ROOT / "automation" / "scripts" / "compliance_optimizer.py"
if not script_path.exists():
raise HTTPException(status_code=500, detail="Optimizer script not found")
try:
result = subprocess.run(
["python", str(script_path), "--topic-id", topic_id],
capture_output=True,
text=True,
timeout=600,
cwd=PROJECT_ROOT
)
if result.returncode != 0:
raise HTTPException(status_code=500, detail=f"Optimizer failed: {result.stderr}")
return {"ok": True, "message": "Optimization completed", "output": result.stdout}
except subprocess.TimeoutExpired:
raise HTTPException(status_code=500, detail="Optimizer timed out")
@router.get("/logs")
def get_logs(
request: Request,
type: str = None, # creator, optimizer, collector
date: str = None, # YYYY-MM-DD
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin)
):
"""
读取系统日志文件内容(管理员)
"""
if not type or not date:
raise HTTPException(status_code=400, detail="type and date parameters are required")
logs_dir = PROJECT_ROOT / "automation" / "logs"
filename = f"{type}_{date}.log"
log_path = logs_dir / filename
if not log_path.exists():
# 日志文件不存在返回空内容
return {"type": type, "date": date, "content": ""}
try:
content = log_path.read_text(encoding="utf-8")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to read log: {str(e)}")
return {"type": type, "date": date, "content": content}
+8 -20
View File
@@ -5,13 +5,13 @@ from pydantic import BaseModel
from datetime import datetime
from typing import Optional
from app.database import get_db
from app.models import Topic, PublishRecord, User
from ..database import get_db
from ..models import Topic, PublishRecord, User
from sqlalchemy.orm import Session
from .auth import verify_token
from .auth import get_current_admin
from ..core.audit_logger import audit_log
router = APIRouter()
router = APIRouter(prefix="/api/publishing", tags=["publishing"])
class PublishRequest(BaseModel):
topic_id: str
@@ -21,26 +21,14 @@ class PublishResponse(BaseModel):
topic_id: str
message: str
def get_current_user(request: Request, db: Session = Depends(get_db)) -> User:
"""获取当前登录用户(可选,未登录也允许,但记录为 anonymous)"""
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return None
token = auth_header.split(" ")[1]
try:
from .auth import verify_token
return verify_token(token, db)
except Exception:
return None
@router.post("/api/publishing/create", response_model=PublishResponse)
@router.post("/create", response_model=PublishResponse)
async def create_publish_record(
req: PublishRequest,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
current_user: User = Depends(get_current_admin)
):
"""标记选题为已发布,并创建发布记录"""
"""标记选题为已发布,并创建发布记录(管理员)"""
try:
# 查找选题
topic = db.query(Topic).filter(Topic.id == req.topic_id).first()
@@ -56,7 +44,7 @@ async def create_publish_record(
topic.published_at = datetime.now().date() # 设置发布时间为今天
# 创建发布记录
operator = current_user.username if current_user else 'anonymous'
operator = current_user.username
record = PublishRecord(
topic_id=req.topic_id,
platform='all',
+9 -9
View File
@@ -29,12 +29,12 @@ def get_status(db: Session = Depends(get_db)):
by_status_result = db.query(Topic.status, func.count()).group_by(Topic.status).all()
by_status = {status: count for status, count in by_status_result}
# 确保返回所有状态
# 确保返回所有状态(数据库存中文,返回前端需要英文)
status_map = {
'pending': by_status.get('pending', 0),
'review': by_status.get('review', 0),
'ready': by_status.get('ready', 0),
'published': by_status.get('published', 0)
'pending': by_status.get('待处理', 0),
'review': by_status.get('待审查', 0),
'ready': by_status.get('待发布', 0),
'published': by_status.get('已发布', 0)
}
# 计算今日新增
@@ -46,10 +46,10 @@ def get_status(db: Session = Depends(get_db)):
return {
"stats": {
"total": total,
"pending": status_map['待处理'],
"review": status_map['待审查'],
"ready": status_map['待发布'],
"published": status_map['已发布'],
"pending": status_map['pending'],
"review": status_map['review'],
"ready": status_map['ready'],
"published": status_map['published'],
"today": today_count
}
}
+3 -2
View File
@@ -9,7 +9,7 @@ from pathlib import Path
from .database import engine, get_db, init_db
from .models import Base
from .api import topics, system, articles, publishing, auth, admin, audit
from .api import topics, system, articles, publishing, auth, admin, audit, optimizer_logs
from .initial_data import import_initial_data
app = FastAPI(title="宇之然内容创作平台", version="0.1.0")
@@ -37,9 +37,10 @@ app.include_router(publishing.router)
app.include_router(auth.router)
app.include_router(admin.router)
app.include_router(audit.router)
app.include_router(optimizer_logs.router)
# 挂载前端
FRONTEND_DIR = Path(__file__).parent.parent / "static"
FRONTEND_DIR = Path(__file__).parent.parent.parent / "frontend"
if FRONTEND_DIR.exists() and (FRONTEND_DIR / "index.html").exists():
app.mount("/", StaticFiles(directory=str(FRONTEND_DIR), html=True), name="frontend")
logger.info(f"Frontend mounted at / from {FRONTEND_DIR}")
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192">
<rect width="192" height="192" fill="#409EFF" rx="24"/>
<text x="96" y="120" font-family="Arial, sans-serif" font-size="80" font-weight="bold" fill="white" text-anchor="middle"></text>
</svg>

Before

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<rect width="512" height="512" fill="#409EFF" rx="48"/>
<text x="256" y="320" font-family="Arial, sans-serif" font-size="200" font-weight="bold" fill="white" text-anchor="middle"></text>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

-170
View File
@@ -1,170 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宇之然内容创作平台</title>
<link rel="stylesheet" href="/static/element-plus/index.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; }
.login-container { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
.login-box { background: white; border-radius: 12px; padding: 40px; width: 100%; max-width: 420px; box-shadow: 0 8px 32px rgba(0,0,0,0.15); }
.login-title { text-align: center; margin-bottom: 32px; color: #303133; font-size: 24px; font-weight: 600; }
.login-btn { width: 100%; }
.app-container { min-height: 100vh; display: flex; flex-direction: column; }
.navbar { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; padding: 16px 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.navbar-content { display: flex; justify-content: space-between; align-items: center; max-width: 1400px; margin: 0 auto; }
.navbar-title { font-size: 20px; font-weight: 600; }
.navbar-user { display: flex; align-items: center; gap: 16px; }
.user-info { display: flex; align-items: center; gap: 8px; }
.avatar { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
.main-content { display: flex; flex: 1; max-width: 1400px; margin: 0 auto; width: 100%; }
.sidebar { width: 180px; background: white; padding: 16px; box-shadow: 2px 0 8px rgba(0,0,0,0.05); }
.sidebar-btn { width: 100%; text-align: left; padding: 12px 16px; border: none; background: transparent; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; }
.sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
.page { display: none; }
.page.active { display: block; }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
.stat-card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); transition: all 0.3s; cursor: pointer; }
.stat-card:hover { transform: translateY(-4px); box-shadow: 0 4px 16px rgba(0,0,0,0.12); }
.stat-title { font-size: 14px; color: #909399; margin-bottom: 8px; }
.stat-value { font-size: 28px; font-weight: 700; color: #303133; }
.stat-card.primary .stat-value { color: #409eff; }
.stat-card.success .stat-value { color: #67c23a; }
.stat-card.warning .stat-value { color: #e6a23c; }
.stat-card.danger .stat-value { color: #f56c6c; }
.stat-card.info .stat-value { color: #909399; }
.module-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
.module-card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.module-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.module-title { font-size: 16px; font-weight: 600; color: #303133; }
.module-status { padding: 4px 12px; border-radius: 20px; font-size: 12px; }
.module-status.running { background: #f0f9ff; color: #409eff; }
.module-content { font-size: 14px; color: #606266; line-height: 1.6; }
.mobile-nav { display: none; position: fixed; bottom: 0; left: 0; right: 0; background: white; box-shadow: 0 -2px 8px rgba(0,0,0,0.1); padding: 8px 0; z-index: 1000; }
.mobile-nav-btn { flex: 1; border: none; background: transparent; padding: 12px; text-align: center; font-size: 12px; color: #606266; cursor: pointer; }
.mobile-nav-btn.active { color: #409eff; font-weight: 600; }
@media (max-width: 768px) {
.sidebar { display: none; }
.mobile-nav { display: flex; }
.content-area { padding: 16px; padding-bottom: 80px; }
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.stat-value { font-size: 24px; }
}
</style>
</head>
<body>
<div id="app">
<div v-if="!isLoggedIn" class="login-container">
<div class="login-box">
<h1 class="login-title">宇之然内容创作平台</h1>
<el-form :model="loginForm" label-width="0">
<el-form-item><el-input v-model="loginForm.username" placeholder="用户名" size="large" prefix-icon="User"></el-input></el-form-item>
<el-form-item><el-input v-model="loginForm.password" type="password" placeholder="密码" size="large" prefix-icon="Lock" @keyup.enter="handleLogin"></el-input></el-form-item>
<el-form-item><el-button type="primary" size="large" class="login-btn" @click="handleLogin" :loading="loginLoading">登录</el-button></el-form-item>
<el-alert v-if="loginError" type="error" :title="loginError" show-icon :closable="false" style="margin-top: 16px;"></el-alert>
</el-form>
</div>
</div>
<div v-else class="app-container">
<nav class="navbar">
<div class="navbar-content">
<h1 class="navbar-title">宇之然内容创作平台</h1>
<div class="navbar-user">
<div class="user-info"><div class="avatar">{{ currentUser.username.charAt(0).toUpperCase() }}</div><span>{{ currentUser.username }}</span><el-tag size="small" v-if="isAdmin" type="danger">管理员</el-tag></div>
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
</div>
</div>
</nav>
<div class="main-content">
<aside class="sidebar">
<button class="sidebar-btn" :class="{ active: currentPage === 'overview' }" @click="redirectToPage('/')">📊 系统概览</button>
<button class="sidebar-btn" :class="{ active: currentPage === 'topics' }" @click="redirectToPage('topics.html')">📋 选题管理</button>
<button class="sidebar-btn" :class="{ active: currentPage === 'logs' }" @click="redirectToPage('logs.html')">📄 系统日志</button>
<button v-if="isAdmin" class="sidebar-btn" :class="{ active: currentPage === 'users' }" @click="redirectToPage('users.html')">👥 用户管理</button>
</aside>
<main class="content-area">
<div id="page-overview" class="page" :class="{ active: currentPage === 'overview' }">
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 24px; color: #303133;">📊 系统概览</h2>
<div class="stats-grid">
<div class="stat-card primary" @click="goToTopics('')"><div class="stat-title">选题总数</div><div class="stat-value">{{ stats.total }}</div></div>
<div class="stat-card warning" @click="goToTopics('待处理')"><div class="stat-title">待处理</div><div class="stat-value">{{ stats.pending }}</div></div>
<div class="stat-card danger" @click="goToTopics('待审查')"><div class="stat-title">待审查</div><div class="stat-value">{{ stats.review }}</div></div>
<div class="stat-card success" @click="goToTopics('待发布')"><div class="stat-title">待发布</div><div class="stat-value">{{ stats.ready }}</div></div>
<div class="stat-card info" @click="goToTopics('已发布')"><div class="stat-title">已发布</div><div class="stat-value">{{ stats.published }}</div></div>
<div class="stat-card primary" @click="goToTopics('')"><div class="stat-title">今日新增</div><div class="stat-value">{{ stats.today }}</div></div>
</div>
<h3 style="font-size: 18px; font-weight: 600; margin-bottom: 16px; color: #303133;">🔧 模块状态</h3>
<div class="module-grid">
<div class="module-card"><div class="module-header"><span class="module-title">🤖 内容创作引擎</span><span class="module-status running">运行中</span></div><div class="module-content"><div>最后运行:2026-04-27 14:30</div><div>今日任务:12 个</div><div>成功率:95%</div></div></div>
<div class="module-card"><div class="module-header"><span class="module-title">🔍 内容优化器</span><span class="module-status running">运行中</span></div><div class="module-content"><div>最后运行:2026-04-27 14:45</div><div>今日优化:8 个</div><div>平均提升:+12 分</div></div></div>
<div class="module-card"><div class="module-header"><span class="module-title">📡 内容收集器</span><span class="module-status running">运行中</span></div><div class="module-content"><div>最后运行:2026-04-27 14:00</div><div>今日收集:24 个</div><div>来源:8 个平台</div></div></div>
<div class="module-card"><div class="module-header"><span class="module-title">📤 发布管理器</span><span class="module-status running">运行中</span></div><div class="module-content"><div>最后运行:2026-04-27 13:30</div><div>今日发布:5 个</div><div>成功率:100%</div></div></div>
</div>
</div>
<div id="page-topics" class="page" :class="{ active: currentPage === 'topics' }"><div style="text-align: center; padding: 40px;"><el-result icon="info" title="选题管理"><template #extra><el-button type="primary" @click="redirectToPage('topics.html')">进入选题管理页面</el-button></template></el-result></div></div>
<div id="page-logs" class="page" :class="{ active: currentPage === 'logs' }"><div style="text-align: center; padding: 40px;"><el-result icon="info" title="系统日志"><template #extra><el-button type="primary" @click="redirectToPage('logs.html')">进入系统日志页面</el-button></template></el-result></div></div>
<div id="page-users" class="page" :class="{ active: currentPage === 'users' }"><div style="text-align: center; padding: 40px;" v-if="isAdmin"><el-result icon="info" title="用户管理"><template #extra><el-button type="primary" @click="redirectToPage('users.html')">进入用户管理页面</el-button></template></el-result></div><el-empty v-else description="暂无权限访问"></el-empty></div>
</main>
</div>
<nav class="mobile-nav">
<button class="mobile-nav-btn" :class="{ active: currentPage === 'overview' }" @click="redirectToPage('/')">📊 概览</button>
<button class="mobile-nav-btn" :class="{ active: currentPage === 'topics' }" @click="redirectToPage('topics.html')">📋 选题</button>
<button class="mobile-nav-btn" :class="{ active: currentPage === 'logs' }" @click="redirectToPage('logs.html')">📄 日志</button>
<button v-if="isAdmin" class="mobile-nav-btn" :class="{ active: currentPage === 'users' }" @click="redirectToPage('users.html')">👥 用户</button>
</nav>
</div>
</div>
<script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script>
<script>
const App = {
data() { return { isLoggedIn: false, isAdmin: false, currentUser: { username: '' }, currentPage: 'overview', loginForm: { username: '', password: '' }, loginLoading: false, loginError: '', stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 } } },
methods: {
async handleLogin() {
this.loginLoading = true; this.loginError = '';
try {
const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.loginForm) });
if (!response.ok) throw new Error('登录失败');
const data = await response.json();
localStorage.setItem('authToken', data.token);
this.currentUser = data.user;
this.isAdmin = data.user.role === 'admin';
this.isLoggedIn = true;
this.currentPage = 'overview';
this.fetchStats();
} catch (error) { this.loginError = '用户名或密码错误'; }
finally { this.loginLoading = false; }
},
handleLogout() { localStorage.removeItem('authToken'); this.isLoggedIn = false; this.currentUser = { username: '' }; this.isAdmin = false; this.loginForm = { username: '', password: '' }; },
async fetchStats() {
try {
const response = await fetch('/api/system/status', { headers: { 'Authorization': 'Bearer ' + localStorage.getItem('authToken') } });
if (response.ok) { const data = await response.json(); this.stats = data.stats || this.stats; }
} catch (error) {
console.log('获取统计信息失败,使用模拟数据');
this.stats = { total: 47, pending: 8, review: 12, ready: 5, published: 22, today: 3 };
}
},
goToTopics(filter) { const url = filter ? '/topics.html?filter=' + encodeURIComponent(filter) : '/topics.html'; window.location.href = url; },
redirectToPage(page) { window.location.href = '/' + page; }
},
mounted() {
const token = localStorage.getItem('authToken');
if (token) {
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
.then(response => response.ok ? response.json() : Promise.reject())
.then(data => { this.currentUser = data.user; this.isAdmin = data.user.role === 'admin'; this.isLoggedIn = true; this.currentPage = 'overview'; this.fetchStats(); })
.catch(() => localStorage.removeItem('authToken'));
}
}
};
const app = Vue.createApp(App);
app.use(ElementPlus);
app.mount('#app');
</script>
</body>
</html>
+22 -7
View File
@@ -89,17 +89,32 @@
data() { return { isLoggedIn: false, isAdmin: false, currentUser: { username: '' }, logType: 'creator', logDate: '', logContent: '', loadingLogs: false } },
methods: {
async fetchLogs() {
// 验证输入
if (!this.logType || !this.logDate) {
this.$message.warning('请选择日志类型和日期');
return;
}
this.loadingLogs = true;
try {
const logs = {
creator: "2026-04-27 11:45:23 | 成功生成选题 B02 - AI 在内容创作中的应用\n2026-04-27 11:30:15 | 开始生成选题 C03 - 数字化转型案例研究\n2026-04-27 10:30:12 | 创建新选题 A01 - 可持续发展趋势分析",
optimizer: "2026-04-27 12:05:30 | 优化完成选题 C03 - 合规分提升至 78\n2026-04-27 11:50:45 | 优化中选题 B02 - 等待人工审核",
collector: "2026-04-27 10:30:12 | 收集到 3 个新选题\n2026-04-27 09:45:20 | 更新行业热点数据\n2026-04-27 09:00:00 | 启动每日收集任务"
}[this.logType] || '暂无日志数据';
this.logContent = "日志类型:" + this.logType + "\n日期:" + (this.logDate || '今天') + "\n\n" + logs;
const token = localStorage.getItem('authToken');
const response = await fetch(`/api/logs?type=${encodeURIComponent(this.logType)}&date=${encodeURIComponent(this.logDate)}`, {
headers: { 'Authorization': 'Bearer ' + token }
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
const data = await response.json();
// 后端返回: { type, date, content }
this.logContent = `日志类型:${data.type}\n日期:${data.date}\n\n${data.content || '(日志文件为空)'}`;
this.$message.success('日志加载成功');
} catch (error) {
this.$message.error('获取日志失败');
console.error('获取日志失败:', error);
this.$message.error(`获取日志失败: ${error.message}`);
this.logContent = '';
} finally {
this.loadingLogs = false;
}
+138 -27
View File
@@ -212,19 +212,27 @@ const TopicsApp = {
this.loadingTable = true;
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/topics', { headers: { 'Authorization': 'Bearer ' + token } });
if (!response.ok) throw new Error('获取失败');
if (!token) {
this.$message.error('请先登录');
setTimeout(() => window.location.href = '/', 1500);
this.loadingTable = false;
return;
}
const response = await fetch('/api/topics', {
headers: { 'Authorization': 'Bearer ' + token }
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
const data = await response.json();
this.topics = data || [];
this.loadingTable = false;
this.$message.success('选题加载成功');
} catch (error) {
console.log('获取选题失败,使用模拟数据');
this.$message.error('获取选题失败,使用模拟数据');
this.topics = [
{ id: 'A01', title: '可持续发展趋势分析', field: '环保', status: 'pending', compliance_score: 85, created_at: '2026-04-27 10:30', generated_at: null, published_at: null },
{ id: 'B02', title: 'AI 在内容创作中的应用', field: '科技', status: 'review', compliance_score: 92, created_at: '2026-04-27 11:15', generated_at: '2026-04-27 11:45', published_at: null },
{ id: 'C03', title: '数字化转型案例研究', field: '商业', status: 'ready', compliance_score: 78, created_at: '2026-04-27 12:00', generated_at: '2026-04-27 12:30', published_at: '2026-04-27 13:00' }
];
console.error('获取选题失败:', error);
this.$message.error(`获取选题失败: ${error.message}`);
this.topics = [];
} finally {
this.loadingTable = false;
}
},
@@ -242,23 +250,126 @@ const TopicsApp = {
await this.fetchTopics();
},
openPreview(topic) { this.$message.info('预览:' + topic.title); },
async createTopic(topic) {
if (topic.status === 'pending') {
this.$message.success('开始创作' + topic.title);
await this.fetchTopics();
} else { this.$message.info('仅待处理选题可创作'); }
},
async optimizeTopic(topic) {
if (topic.status === 'review') {
this.$message.success('开始优化:' + topic.title);
await this.fetchTopics();
} else { this.$message.info('仅待审查选题可优化'); }
},
async handlePublish(topic) { this.$message.success('发布:' + topic.title); await this.fetchTopics(); },
deleteTopic(id) {
this.$confirm('确定删除?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => { this.$message.success('删除成功'); await this.fetchTopics(); }).catch(() => {});
},
async createTopic(topic) {
if (topic.status !== 'pending') {
this.$message.info('仅待处理选题可创作');
return;
}
try {
const token = localStorage.getItem('authToken');
if (!token) {
this.$message.error('请先登录');
return;
}
const response = await fetch('/api/system/generate/run', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ topic_id: topic.id })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
const data = await response.json();
this.$message.success(`创作完成: ${topic.title}`);
await this.fetchTopics();
} catch (error) {
console.error('创作失败:', error);
this.$message.error(`创作失败: ${error.message}`);
}
},
async optimizeTopic(topic) {
if (topic.status !== 'review') {
this.$message.info('仅待审查选题可优化');
return;
}
try {
const token = localStorage.getItem('authToken');
if (!token) {
this.$message.error('请先登录');
return;
}
const response = await fetch('/api/optimizer/run', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ topic_id: topic.id })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
const data = await response.json();
this.$message.success(`优化完成: ${topic.title}`);
await this.fetchTopics();
} catch (error) {
console.error('优化失败:', error);
this.$message.error(`优化失败: ${error.message}`);
}
},
async handlePublish(topic) {
if (topic.status !== 'ready') {
this.$message.info('仅待发布选题可发布');
return;
}
try {
const token = localStorage.getItem('authToken');
if (!token) {
this.$message.error('请先登录');
return;
}
const response = await fetch('/api/publishing/create', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ topic_id: topic.id })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
const data = await response.json();
this.$message.success(`发布成功: ${topic.title}`);
await this.fetchTopics();
} catch (error) {
console.error('发布失败:', error);
this.$message.error(`发布失败: ${error.message}`);
}
},
async deleteTopic(id) {
this.$confirm('确定删除?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => {
try {
const token = localStorage.getItem('authToken');
if (!token) {
this.$message.error('请先登录');
window.location.href = '/';
return;
}
const response = await fetch(`/api/topics/${encodeURIComponent(id)}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + token }
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
this.$message.success('删除成功');
await this.fetchTopics();
} catch (error) {
console.error('删除失败:', error);
this.$message.error(`删除失败: ${error.message}`);
}
})
.catch(() => {});
},
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + page; },
formatDate(dateStr) {
-415
View File
@@ -1,415 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宇之然内容创作平台 - 选题管理</title>
<link rel="stylesheet" href="/static/element-plus/index.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; }
.navbar { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; padding: 16px 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.navbar-content { display: flex; justify-content: space-between; align-items: center; max-width: 1400px; margin: 0 auto; }
.navbar-title { font-size: 20px; font-weight: 600; }
.navbar-user { display: flex; align-items: center; gap: 16px; }
.user-info { display: flex; align-items: center; gap: 8px; }
.avatar { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
.main-content { display: flex; flex: 1; max-width: 1400px; margin: 0 auto; width: 100%; }
.sidebar { width: 180px; background: white; padding: 16px; box-shadow: 2px 0 8px rgba(0,0,0,0.05); }
.sidebar-btn { width: 100%; text-align: left; padding: 12px 16px; border: none; background: transparent; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; }
.sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
.card { background: white; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.status-badge { display: inline-flex; align-items: center; gap: 4px; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
.status-dot.待处理 { background: #E6A23C; }
.status-dot.待审查 { background: #F56C6C; }
.status-dot.待发布 { background: #67C23A; }
.status-dot.已发布 { background: #409EFF; }
.mobile-nav-btn { flex: 1; border: none; background: transparent; padding: 12px; text-align: center; font-size: 12px; color: #606266; cursor: pointer; }
.mobile-nav-btn.active { color: #409eff; font-weight: 600; }
@media (max-width: 768px) {
.mobile-nav { display: flex !important; }
.topic-card-list { display: block; }
.el-table { display: none; }
.sidebar { display: none; }
.content-area { padding: 16px; padding-bottom: 80px; }
}
/* 移动端卡片布局 */
@media (max-width: 768px) {
.el-table { font-size: 12px; display: none; }
.el-table .el-button { padding: 4px 8px; font-size: 11px; min-height: auto; }
.el-table .cell { padding: 0 4px; }
.el-table .el-table__cell { padding: 6px 0; }
.topic-card {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.topic-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.topic-card-title { font-size: 16px; font-weight: 600; color: #303133; flex: 1; margin-right: 8px; }
.topic-card-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.topic-card-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 12px;
}
.topic-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.topic-card-actions .el-button { flex: 1; min-width: 60px; }
}
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
<style>#app[v-cloak] { display: none; }
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
</head>
<body>
<div id="app" v-cloak>
<nav class="navbar">
<div class="navbar-content">
<h1 class="navbar-title">宇之然内容创作平台 - 选题管理</h1>
<div class="navbar-user">
<div class="user-info"><div class="avatar">{{ currentUser.username ? currentUser.username.charAt(0).toUpperCase() : '?' }}</div><span>{{ currentUser.username }}</span></div>
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
</div>
</div>
</nav>
<div class="main-content">
<aside class="sidebar">
<button class="sidebar-btn" @click="redirectToPage('/')">📊 系统概览</button>
<button class="sidebar-btn active">📋 选题管理</button>
<button class="sidebar-btn" @click="redirectToPage('logs.html')">📄 系统日志</button>
<button v-if="isAdmin" class="sidebar-btn" @click="redirectToPage('users.html')">👥 用户管理</button>
</aside>
<main class="content-area">
<div class="card">
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 24px;">📋 选题管理</h2>
<div class="card" style="display: inline-block; min-width: fit-content; padding: 16px; margin-bottom: 24px;">
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<el-button type="primary" size="small" @click="refreshAll">🔄 批量刷新</el-button>
<el-button type="success" size="small" @click="triggerGenerateSelected" :disabled="selectedTopicIds.length === 0">▶ 批量创作</el-button>
<el-button type="warning" size="small" @click="triggerOptimizeSelected" :disabled="selectedTopicIds.length === 0">🔍 批量优化</el-button>
<span class="ml-auto text-sm text-gray-500" v-if="selectedTopicIds.length > 0" style="color: #909399; font-size: 14px; margin-left: auto;">已选 {{ selectedTopicIds.length }} 项</span>
</div>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap;">
<el-tag size="large" :type="filterStatus === '' ? 'primary' : ''" @click="filterStatus = ''">全部 ({{ topics.length }})</el-tag>
<el-tag size="large" :type="filterStatus === '待处理' ? 'primary' : ''" @click="filterStatus = '待处理'">待处理 ({{ countByStatus('待处理') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待审查' ? 'primary' : ''" @click="filterStatus = '待审查'">待审查 ({{ countByStatus('待审查') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待发布' ? 'primary' : ''" @click="filterStatus = '待发布'">待发布 ({{ countByStatus('待发布') }})</el-tag>
<el-tag size="large" :type="filterStatus === '已发布' ? 'primary' : ''" @click="filterStatus = '已发布'">已发布 ({{ countByStatus('已发布') }})</el-tag>
</div>
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="70" fixed></el-table-column>
<el-table-column prop="title" label="标题" min-width="200"></el-table-column>
<el-table-column prop="field" label="领域" width="100"></el-table-column>
<el-table-column prop="status" label="状态" width="90">
<template #default="scope"><span class="status-badge"><span class="status-dot" :class="scope.row.status"></span>{{ scope.row.status }}</span></template>
</el-table-column>
<el-table-column prop="compliance_score" label="合规分" width="90">
<template #default="scope"><el-progress :percentage="scope.row.compliance_score || 0" :format="() => scope.row.compliance_score || '-'" :stroke-width="15"></el-progress></template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="140"><template #default="scope">{{ formatDate(scope.row.created_at) }}</template></el-table-column>
<el-table-column prop="generated_at" label="创作时间" width="140"><template #default="scope">{{ scope.row.generated_at ? formatDate(scope.row.generated_at) : '-' }}</template></el-table-column>
<el-table-column prop="published_at" label="发布时间" width="140"><template #default="scope">{{ scope.row.published_at ? formatDate(scope.row.published_at) : '-' }}</template></el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
<el-button size="small" @click="openPreview(scope.row)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="scope.row.status !== '待处理'" @click="createTopic(scope.row)">创作</el-button>
<el-button size="small" type="warning" :disabled="scope.row.status !== '待审查'" @click="optimizeTopic(scope.row)">审查</el-button>
<el-button v-if="scope.row.status === '待发布'" size="small" type="primary" @click="handlePublish(scope.row)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 移动端卡片列表 -->
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card">
<div class="topic-card-header">
<div class="topic-card-title">{{ topic.title }}</div>
<el-tag :type="getStatusType(topic.status)" size="small">{{ topic.status }}</el-tag>
</div>
<div class="topic-card-tags">
<el-tag size="small" type="info">{{ topic.field }}</el-tag>
<el-tag size="small" type="warning">合规{{ topic.compliance_score }}</el-tag>
</div>
<div class="topic-card-meta">
<div>创建: {{ formatDate(topic.created_at) }}</div>
<div>创作: {{ topic.generated_at ? formatDate(topic.generated_at) : '-' }}</div>
<div>发布: {{ topic.published_at ? formatDate(topic.published_at) : '-' }}</div>
</div>
<div class="topic-card-actions">
<el-button size="small" @click="openPreview(topic)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="topic.status !== '待处理'" @click="createTopic(topic)">创作</el-button>
<el-button size="small" type="warning" :disabled="topic.status !== '待审查'" @click="optimizeTopic(topic)">审查</el-button>
<el-button v-if="topic.status === '待发布'" size="small" type="primary" @click="handlePublish(topic)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<nav class="mobile-nav">
<button class="mobile-nav-btn" @click="redirectToPage('/')">📊 概览</button>
<button class="mobile-nav-btn active">📋 选题</button>
<button class="mobile-nav-btn" @click="redirectToPage('logs.html')">📄 日志</button>
<button v-if="isAdmin" class="mobile-nav-btn" @click="redirectToPage('users.html')">👥 用户</button>
</nav>
</div>
<script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script>
<script>
const TopicsApp = {
data() {
// 从 localStorage 恢复登录状态
let isLoggedIn = false, isAdmin = false, currentUser = { username: '' };
try {
const userInfo = JSON.parse(localStorage.getItem('user_info'));
if (userInfo) {
isLoggedIn = true;
isAdmin = userInfo.role === 'admin';
currentUser = userInfo;
}
} catch (e) {}
return {
currentPage: 'topics',
isLoggedIn, isAdmin, currentUser,
loadingTable: false,
selectedTopicIds: [],
filterStatus: '',
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
topics: []
};
},
computed: {
filteredTopics() {
if (!this.topics || !this.topics.length) { return []; }
if (!this.filterStatus) { return this.topics; }
return this.topics.filter(t => t.status === this.filterStatus);
},
countByStatus() {
return (status) => this.topics.filter(t => t.status === status).length;
}
},
methods: {
async fetchTopics() {
this.loadingTable = true;
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/topics/?size=100', { headers: { 'Authorization': 'Bearer ' + token } });
if (!response.ok) throw new Error('获取失败');
const data = await response.json();
this.topics = data || [];
this.loadingTable = false;
} catch (error) {
console.log('获取选题失败,使用模拟数据');
this.$message.error('获取选题失败,使用模拟数据');
this.topics = [
{ id: 'A01', title: '可持续发展趋势分析', field: '环保', status: '待处理', compliance_score: 85, created_at: '2026-04-27 10:30', generated_at: null, published_at: null },
{ id: 'B02', title: 'AI 在内容创作中的应用', field: '科技', status: '待审查', compliance_score: 92, created_at: '2026-04-27 11:15', generated_at: '2026-04-27 11:45', published_at: null },
{ id: 'C03', title: '数字化转型案例研究', field: '商业', status: '待发布', compliance_score: 78, created_at: '2026-04-27 12:00', generated_at: '2026-04-27 12:30', published_at: '2026-04-27 13:00' }
];
this.loadingTable = false;
}
},
async fetchStats() {
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/system/status', { headers: { 'Authorization': 'Bearer ' + token } });
if (response.ok) {
const data = await response.json();
this.stats = {
total: data.total_topics || 0,
pending: data.topics_by_status ? data.topics_by_status['待处理'] || 0 : 0,
review: data.topics_by_status ? data.topics_by_status['待审查'] || 0 : 0,
ready: data.topics_by_status ? data.topics_by_status['待发布'] || 0 : 0,
published: data.published_count || 0,
today: data.today_articles || 0
};
}
} catch (error) {
console.log('获取统计信息失败');
}
},
refreshAll() { this.$message.info('执行批量刷新'); },
async triggerGenerateSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量创作已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async triggerOptimizeSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量优化已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
} async createTopic(topic) {
if (topic.status === '待处理') {
this.$message.success('开始创作:' + topic.title);
await this.fetchTopics();
} else { this.$message.info('仅待处理选题可创作'); }
},
async optimizeTopic(topic) {
if (topic.status === '待审查') {
this.$message.success('开始优化:' + topic.title);
await this.fetchTopics();
} else { this.$message.info('仅待审查选题可优化'); }
},
async handlePublish(topic) {
if (topic.status !== '待发布') {
this.$message.info('仅待发布选题可发布');
return;
}
try {
const response = await fetch('/api/publishing/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ topic_id: topic.id })
});
if (response.ok) {
const data = await response.json();
this.$message.success('发布成功');
if (data.urls) {
const msg = Object.entries(data.urls).map(([k,v]) => `${k}: ${v}`).join('
');
this.$notify({ title: '发布链接', message: `<pre>${msg}</pre>`, type: 'success', duration: 0 });
}
} else {
throw new Error('发布失败');
}
await this.fetchTopics();
} catch (e) {
this.$message.error('发布失败');
}
},
deleteTopic(id) {
this.$confirm('确定删除?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => {
try {
const response = await fetch(`/api/topics/${id}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('authToken') }
});
if (response.ok) {
this.$message.success('删除成功');
await this.fetchTopics();
} else {
const err = await response.json().catch(() => ({}));
this.$message.error('删除失败: ' + (err.detail || response.statusText));
}
} catch (e) {
this.$message.error('删除请求失败');
}
})
.catch(() => {});
},
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + page; },
formatDate(dateStr) {
if (!dateStr) return '-';
try {
return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
});
} catch (e) { return dateStr; }
},
getStatusType(status) {
const map = { '待处理': 'warning', '待审查': 'danger', '待发布': 'success', '已发布': 'info' };
return map[status] || 'primary';
,
openPreview(topic) {
const urls = topic.platform_urls && Object.entries(topic.platform_urls).map(([k, v]) => `${k}: ${v}`).join(', ');
const details = [
`选题ID: ${topic.id}`,
`标题: ${topic.title}`,
`领域: ${topic.field || '-'}`,
`优先级: ${topic.priority_score}`,
`状态: ${topic.status}`,
`合规分: ${topic.compliance_score || '-'}`,
`创建时间: ${topic.created_at || '-'}`,
`发布时间: ${topic.published_at || '-'}`,
`生成时间: ${topic.generated_at || '-'}`,
`发布平台: ${urls || '未发布'}`
].join('\n');
this.$alert(details, '选题预览', { width: '600px', customClass: 'preview-dialog' });
},
}
},
mounted() {
console.log('[DEBUG] TopicsApp mounted');
const token = localStorage.getItem('authToken');
console.log('[DEBUG] Token exists:', !!token);
if (!token) { window.location.href = '/'; return; }
// 解析 URL filter 参数
const urlParams = new URLSearchParams(window.location.search);
const filter = urlParams.get('filter');
console.log('[DEBUG] URL filter:', filter);
if (filter) { this.filterStatus = filter; }
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
.then(response => response.ok ? response.json() : Promise.reject())
.then(data => {
console.log('[DEBUG] Auth success, user:', data.user);
this.currentUser = data.user;
this.isAdmin = data.user.role === 'admin';
this.isLoggedIn = true;
this.fetchTopics();
})
.catch((err) => {
console.error('[DEBUG] Auth failed in topics.html', err);
localStorage.removeItem('authToken');
window.location.href = '/';
});
}
};
const app = Vue.createApp(TopicsApp);
app.use(ElementPlus);
app.mount('#app');
</script>
</body>
</html>
@@ -1,430 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宇之然内容创作平台 - 选题管理</title>
<link rel="stylesheet" href="/static/element-plus/index.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; }
.navbar { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; padding: 16px 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.navbar-content { display: flex; justify-content: space-between; align-items: center; max-width: 1400px; margin: 0 auto; }
.navbar-title { font-size: 20px; font-weight: 600; }
.navbar-user { display: flex; align-items: center; gap: 16px; }
.user-info { display: flex; align-items: center; gap: 8px; }
.avatar { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
.main-content { display: flex; flex: 1; max-width: 1400px; margin: 0 auto; width: 100%; }
.sidebar { width: 180px; background: white; padding: 16px; box-shadow: 2px 0 8px rgba(0,0,0,0.05); }
.sidebar-btn { width: 100%; text-align: left; padding: 12px 16px; border: none; background: transparent; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; }
.sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
.card { background: white; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.status-badge { display: inline-flex; align-items: center; gap: 4px; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
.status-dot.待处理 { background: #E6A23C; }
.status-dot.待审查 { background: #F56C6C; }
.status-dot.待发布 { background: #67C23A; }
.status-dot.已发布 { background: #409EFF; }
.mobile-nav-btn { flex: 1; border: none; background: transparent; padding: 12px; text-align: center; font-size: 12px; color: #606266; cursor: pointer; }
.mobile-nav-btn.active { color: #409eff; font-weight: 600; }
@media (max-width: 768px) {
.mobile-nav { display: flex !important; }
.topic-card-list { display: block; }
.el-table { display: none; }
.sidebar { display: none; }
.content-area { padding: 16px; padding-bottom: 80px; }
}
/* 移动端卡片布局 */
@media (max-width: 768px) {
.el-table { font-size: 12px; display: none; }
.el-table .el-button { padding: 4px 8px; font-size: 11px; min-height: auto; }
.el-table .cell { padding: 0 4px; }
.el-table .el-table__cell { padding: 6px 0; }
.topic-card {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.topic-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.topic-card-title { font-size: 16px; font-weight: 600; color: #303133; flex: 1; margin-right: 8px; }
.topic-card-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.topic-card-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 12px;
}
.topic-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.topic-card-actions .el-button { flex: 1; min-width: 60px; }
}
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
<style>#app[v-cloak] { display: none; }
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
</head>
<body>
<div id="app" v-cloak>
<nav class="navbar">
<div class="navbar-content">
<h1 class="navbar-title">宇之然内容创作平台 - 选题管理</h1>
<div class="navbar-user">
<div class="user-info"><div class="avatar">{{ currentUser.username ? currentUser.username.charAt(0).toUpperCase() : '?' }}</div><span>{{ currentUser.username }}</span></div>
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
</div>
</div>
</nav>
<div class="main-content">
<aside class="sidebar">
<button class="sidebar-btn" @click="redirectToPage('/')">📊 系统概览</button>
<button class="sidebar-btn active">📋 选题管理</button>
<button class="sidebar-btn" @click="redirectToPage('logs.html')">📄 系统日志</button>
<button v-if="isAdmin" class="sidebar-btn" @click="redirectToPage('users.html')">👥 用户管理</button>
</aside>
<main class="content-area">
<div class="card">
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 24px;">📋 选题管理</h2>
<div class="card" style="display: inline-block; min-width: fit-content; padding: 16px; margin-bottom: 24px;">
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<el-button type="primary" size="small" @click="refreshAll">🔄 批量刷新</el-button>
<el-button type="success" size="small" @click="triggerGenerateSelected" :disabled="selectedTopicIds.length === 0">▶ 批量创作</el-button>
<el-button type="warning" size="small" @click="triggerOptimizeSelected" :disabled="selectedTopicIds.length === 0">🔍 批量优化</el-button>
<span class="ml-auto text-sm text-gray-500" v-if="selectedTopicIds.length > 0" style="color: #909399; font-size: 14px; margin-left: auto;">已选 {{ selectedTopicIds.length }} 项</span>
</div>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap;">
<el-tag size="large" :type="filterStatus === '' ? 'primary' : ''" @click="filterStatus = ''">全部 ({{ topics.length }})</el-tag>
<el-tag size="large" :type="filterStatus === '待处理' ? 'primary' : ''" @click="filterStatus = '待处理'">待处理 ({{ countByStatus('待处理') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待审查' ? 'primary' : ''" @click="filterStatus = '待审查'">待审查 ({{ countByStatus('待审查') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待发布' ? 'primary' : ''" @click="filterStatus = '待发布'">待发布 ({{ countByStatus('待发布') }})</el-tag>
<el-tag size="large" :type="filterStatus === '已发布' ? 'primary' : ''" @click="filterStatus = '已发布'">已发布 ({{ countByStatus('已发布') }})</el-tag>
</div>
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="70" fixed></el-table-column>
<el-table-column prop="title" label="标题" min-width="200"></el-table-column>
<el-table-column prop="field" label="领域" width="100"></el-table-column>
<el-table-column prop="status" label="状态" width="90">
<template #default="scope"><span class="status-badge"><span class="status-dot" :class="scope.row.status"></span>{{ scope.row.status }}</span></template>
</el-table-column>
<el-table-column prop="compliance_score" label="合规分" width="90">
<template #default="scope"><el-progress :percentage="scope.row.compliance_score || 0" :format="() => scope.row.compliance_score || '-'" :stroke-width="15"></el-progress></template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="140"><template #default="scope">{{ formatDate(scope.row.created_at) }}</template></el-table-column>
<el-table-column prop="generated_at" label="创作时间" width="140"><template #default="scope">{{ scope.row.generated_at ? formatDate(scope.row.generated_at) : '-' }}</template></el-table-column>
<el-table-column prop="published_at" label="发布时间" width="140"><template #default="scope">{{ scope.row.published_at ? formatDate(scope.row.published_at) : '-' }}</template></el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
<el-button size="small" @click="openPreview(scope.row)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="scope.row.status !== '待处理'" @click="createTopic(scope.row)">创作</el-button>
<el-button size="small" type="warning" :disabled="scope.row.status !== '待审查'" @click="optimizeTopic(scope.row)">审查</el-button>
<el-button v-if="scope.row.status === '待发布'" size="small" type="primary" @click="handlePublish(scope.row)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 移动端卡片列表 -->
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card">
<div class="topic-card-header">
<div class="topic-card-title">{{ topic.title }}</div>
<el-tag :type="getStatusType(topic.status)" size="small">{{ topic.status }}</el-tag>
</div>
<div class="topic-card-tags">
<el-tag size="small" type="info">{{ topic.field }}</el-tag>
<el-tag size="small" type="warning">合规{{ topic.compliance_score }}</el-tag>
</div>
<div class="topic-card-meta">
<div>创建: {{ formatDate(topic.created_at) }}</div>
<div>创作: {{ topic.generated_at ? formatDate(topic.generated_at) : '-' }}</div>
<div>发布: {{ topic.published_at ? formatDate(topic.published_at) : '-' }}</div>
</div>
<div class="topic-card-actions">
<el-button size="small" @click="openPreview(topic)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="topic.status !== '待处理'" @click="createTopic(topic)">创作</el-button>
<el-button size="small" type="warning" :disabled="topic.status !== '待审查'" @click="optimizeTopic(topic)">审查</el-button>
<el-button v-if="topic.status === '待发布'" size="small" type="primary" @click="handlePublish(topic)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<nav class="mobile-nav">
<button class="mobile-nav-btn" @click="redirectToPage('/')">📊 概览</button>
<button class="mobile-nav-btn active">📋 选题</button>
<button class="mobile-nav-btn" @click="redirectToPage('logs.html')">📄 日志</button>
<button v-if="isAdmin" class="mobile-nav-btn" @click="redirectToPage('users.html')">👥 用户</button>
</nav>
</div>
<script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script>
<script>
const TopicsApp = {
data() {
return {
isLoggedIn: !!localStorage.getItem('authToken'),
isAdmin: false,
currentUser: { username: '' },
loadingTable: false,
selectedTopicIds: [],
filterStatus: '',
topics: [],
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 }
};
},
computed: {
filteredTopics() {
if (!this.topics || !this.topics.length) { return []; }
if (!this.filterStatus) { return this.topics; }
return this.topics.filter(t => t.status === this.filterStatus);
},
countByStatus() {
return (status) => this.topics.filter(t => t.status === status).length;
}
},
methods: {
async fetchTopics() {
this.loadingTable = true;
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/topics/?size=100', { headers: { 'Authorization': 'Bearer ' + token } });
if (!response.ok) throw new Error('获取失败');
const data = await response.json();
this.topics = data || [];
this.loadingTable = false;
} catch (error) {
console.log('获取选题失败,使用模拟数据');
this.$message.error('获取选题失败,使用模拟数据');
this.topics = [
{ id: 'A01', title: '可持续发展趋势分析', field: '环保', status: '待处理', compliance_score: 85, created_at: '2026-04-27 10:30', generated_at: null, published_at: null },
{ id: 'B02', title: 'AI 在内容创作中的应用', field: '科技', status: '待审查', compliance_score: 92, created_at: '2026-04-27 11:15', generated_at: '2026-04-27 11:45', published_at: null },
{ id: 'C03', title: '数字化转型案例研究', field: '商业', status: '待发布', compliance_score: 78, created_at: '2026-04-27 12:00', generated_at: '2026-04-27 12:30', published_at: null }
];
this.loadingTable = false;
}
},
async fetchStats() {
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/system/status', { headers: { 'Authorization': 'Bearer ' + token } });
if (response.ok) {
const data = await response.json();
this.stats = {
total: data.total_topics || 0,
pending: data.topics_by_status ? data.topics_by_status['待处理'] || 0 : 0,
review: data.topics_by_status ? data.topics_by_status['待审查'] || 0 : 0,
ready: data.topics_by_status ? data.topics_by_status['待发布'] || 0 : 0,
published: data.published_count || 0,
today: data.today_articles || 0
};
}
} catch (error) {
console.log('获取统计信息失败');
}
},
refreshAll() { this.$message.info('执行批量刷新'); },
async triggerGenerateSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量创作已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async triggerOptimizeSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量优化已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async createTopic(topic) {
if (topic.status !== '待处理') {
this.$message.info('仅待处理选题可创作');
return;
}
try {
await fetch(`/api/topics/${topic.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ status: '待审查' })
});
this.$message.success('创作完成,状态变更为待审查');
await this.fetchTopics();
} catch (e) {
this.$message.error('创作失败');
}
},
async optimizeTopic(topic) {
if (topic.status !== '待审查') {
this.$message.info('仅待审查选题可优化');
return;
}
try {
await fetch(`/api/topics/${topic.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ status: '待发布' })
});
this.$message.success('优化完成,状态变更为待发布');
await this.fetchTopics();
} catch (e) {
this.$message.error('优化失败');
}
},
async handlePublish(topic) {
if (topic.status !== '待发布') {
this.$message.info('仅待发布选题可发布');
return;
}
try {
const response = await fetch('/api/publishing/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ topic_id: topic.id })
});
if (response.ok) {
const data = await response.json();
this.$message.success('发布成功');
if (data.urls) {
const msg = Object.entries(data.urls).map(([k,v]) => `${k}: ${v}`).join('\n');
this.$notify({ title: '发布链接', message: `<pre>${msg}</pre>`, type: 'success', duration: 0 });
}
} else {
throw new Error('发布失败');
}
await this.fetchTopics();
} catch (e) {
this.$message.error('发布失败');
}
},
deleteTopic(id) {
this.$confirm('确定删除?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => {
try {
const response = await fetch(`/api/topics/${id}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('authToken') }
});
if (response.ok) {
this.$message.success('删除成功');
await this.fetchTopics();
} else {
const err = await response.json().catch(() => ({}));
this.$message.error('删除失败: ' + (err.detail || response.statusText));
}
} catch (e) {
this.$message.error('删除请求失败');
}
})
.catch(() => {});
},
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
openPreview(topic) {
const lines = [
`选题ID: ${topic.id}`,
`标题: ${topic.title}`,
`领域: ${topic.field || '-'}`,
`优先级: ${topic.priority_score}`,
`状态: ${topic.status}`,
`合规分: ${topic.compliance_score || '-'}`,
`创建时间: ${topic.created_at || '-'}`,
`发布时间: ${topic.published_at || '-'}`,
`生成时间: ${topic.generated_at || '-'}`
];
const platform = topic.platform_urls && Object.entries(topic.platform_urls).map(([k, v]) => `${k}: ${v}`).join(', ');
lines.push(`发布平台: ${platform || '未发布'}`);
this.$alert(lines.join('\n'), '选题预览', { width: '600px', customClass: 'preview-dialog' });
},
redirectToPage(page) {
let url = page;
if (page === 'overview' || page === '/') url = '/';
else if (!page.endsWith('.html')) url = page + '.html';
window.location.href = url;
},
formatDate(dateStr) {
if (!dateStr) return '-';
try {
return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
});
} catch (e) { return dateStr; }
},
getStatusType(status) {
const map = { '待处理': 'warning', '待审查': 'danger', '待发布': 'success', '已发布': 'info' };
return map[status] || 'primary';
}
},
mounted() {
console.log('[DEBUG] TopicsApp mounted');
const token = localStorage.getItem('authToken');
console.log('[DEBUG] Token exists:', !!token);
if (!token) { window.location.href = '/'; return; }
const urlParams = new URLSearchParams(window.location.search);
const filter = urlParams.get('filter');
console.log('[DEBUG] URL filter:', filter);
if (filter) { this.filterStatus = filter; }
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
.then(response => response.ok ? response.json() : Promise.reject())
.then(data => {
console.log('[DEBUG] Auth success, user:', data.user);
this.currentUser = data.user;
})
.catch(() => { console.log('[DEBUG] Auth failed'); this.currentUser = {}; });
this.fetchTopics();
this.fetchStats();
}
};
TopicsApp = new Vue({ ... }); // placeholder, actual template may have its own Nuxt/Vue init
</script>
</body>
</html>
@@ -1,430 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宇之然内容创作平台 - 选题管理</title>
<link rel="stylesheet" href="/static/element-plus/index.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; }
.navbar { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; padding: 16px 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.navbar-content { display: flex; justify-content: space-between; align-items: center; max-width: 1400px; margin: 0 auto; }
.navbar-title { font-size: 20px; font-weight: 600; }
.navbar-user { display: flex; align-items: center; gap: 16px; }
.user-info { display: flex; align-items: center; gap: 8px; }
.avatar { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
.main-content { display: flex; flex: 1; max-width: 1400px; margin: 0 auto; width: 100%; }
.sidebar { width: 180px; background: white; padding: 16px; box-shadow: 2px 0 8px rgba(0,0,0,0.05); }
.sidebar-btn { width: 100%; text-align: left; padding: 12px 16px; border: none; background: transparent; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; }
.sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
.card { background: white; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.status-badge { display: inline-flex; align-items: center; gap: 4px; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
.status-dot.待处理 { background: #E6A23C; }
.status-dot.待审查 { background: #F56C6C; }
.status-dot.待发布 { background: #67C23A; }
.status-dot.已发布 { background: #409EFF; }
.mobile-nav-btn { flex: 1; border: none; background: transparent; padding: 12px; text-align: center; font-size: 12px; color: #606266; cursor: pointer; }
.mobile-nav-btn.active { color: #409eff; font-weight: 600; }
@media (max-width: 768px) {
.mobile-nav { display: flex !important; }
.topic-card-list { display: block; }
.el-table { display: none; }
.sidebar { display: none; }
.content-area { padding: 16px; padding-bottom: 80px; }
}
/* 移动端卡片布局 */
@media (max-width: 768px) {
.el-table { font-size: 12px; display: none; }
.el-table .el-button { padding: 4px 8px; font-size: 11px; min-height: auto; }
.el-table .cell { padding: 0 4px; }
.el-table .el-table__cell { padding: 6px 0; }
.topic-card {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.topic-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.topic-card-title { font-size: 16px; font-weight: 600; color: #303133; flex: 1; margin-right: 8px; }
.topic-card-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.topic-card-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 12px;
}
.topic-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.topic-card-actions .el-button { flex: 1; min-width: 60px; }
}
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
<style>#app[v-cloak] { display: none; }
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
</head>
<body>
<div id="app" v-cloak>
<nav class="navbar">
<div class="navbar-content">
<h1 class="navbar-title">宇之然内容创作平台 - 选题管理</h1>
<div class="navbar-user">
<div class="user-info"><div class="avatar">{{ currentUser.username ? currentUser.username.charAt(0).toUpperCase() : '?' }}</div><span>{{ currentUser.username }}</span></div>
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
</div>
</div>
</nav>
<div class="main-content">
<aside class="sidebar">
<button class="sidebar-btn" @click="redirectToPage('/')">📊 系统概览</button>
<button class="sidebar-btn active">📋 选题管理</button>
<button class="sidebar-btn" @click="redirectToPage('logs.html')">📄 系统日志</button>
<button v-if="isAdmin" class="sidebar-btn" @click="redirectToPage('users.html')">👥 用户管理</button>
</aside>
<main class="content-area">
<div class="card">
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 24px;">📋 选题管理</h2>
<div class="card" style="display: inline-block; min-width: fit-content; padding: 16px; margin-bottom: 24px;">
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<el-button type="primary" size="small" @click="refreshAll">🔄 批量刷新</el-button>
<el-button type="success" size="small" @click="triggerGenerateSelected" :disabled="selectedTopicIds.length === 0">▶ 批量创作</el-button>
<el-button type="warning" size="small" @click="triggerOptimizeSelected" :disabled="selectedTopicIds.length === 0">🔍 批量优化</el-button>
<span class="ml-auto text-sm text-gray-500" v-if="selectedTopicIds.length > 0" style="color: #909399; font-size: 14px; margin-left: auto;">已选 {{ selectedTopicIds.length }} 项</span>
</div>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap;">
<el-tag size="large" :type="filterStatus === '' ? 'primary' : ''" @click="filterStatus = ''">全部 ({{ topics.length }})</el-tag>
<el-tag size="large" :type="filterStatus === '待处理' ? 'primary' : ''" @click="filterStatus = '待处理'">待处理 ({{ countByStatus('待处理') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待审查' ? 'primary' : ''" @click="filterStatus = '待审查'">待审查 ({{ countByStatus('待审查') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待发布' ? 'primary' : ''" @click="filterStatus = '待发布'">待发布 ({{ countByStatus('待发布') }})</el-tag>
<el-tag size="large" :type="filterStatus === '已发布' ? 'primary' : ''" @click="filterStatus = '已发布'">已发布 ({{ countByStatus('已发布') }})</el-tag>
</div>
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="70" fixed></el-table-column>
<el-table-column prop="title" label="标题" min-width="200"></el-table-column>
<el-table-column prop="field" label="领域" width="100"></el-table-column>
<el-table-column prop="status" label="状态" width="90">
<template #default="scope"><span class="status-badge"><span class="status-dot" :class="scope.row.status"></span>{{ scope.row.status }}</span></template>
</el-table-column>
<el-table-column prop="compliance_score" label="合规分" width="90">
<template #default="scope"><el-progress :percentage="scope.row.compliance_score || 0" :format="() => scope.row.compliance_score || '-'" :stroke-width="15"></el-progress></template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="140"><template #default="scope">{{ formatDate(scope.row.created_at) }}</template></el-table-column>
<el-table-column prop="generated_at" label="创作时间" width="140"><template #default="scope">{{ scope.row.generated_at ? formatDate(scope.row.generated_at) : '-' }}</template></el-table-column>
<el-table-column prop="published_at" label="发布时间" width="140"><template #default="scope">{{ scope.row.published_at ? formatDate(scope.row.published_at) : '-' }}</template></el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
<el-button size="small" @click="openPreview(scope.row)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="scope.row.status !== '待处理'" @click="createTopic(scope.row)">创作</el-button>
<el-button size="small" type="warning" :disabled="scope.row.status !== '待审查'" @click="optimizeTopic(scope.row)">审查</el-button>
<el-button v-if="scope.row.status === '待发布'" size="small" type="primary" @click="handlePublish(scope.row)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 移动端卡片列表 -->
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card">
<div class="topic-card-header">
<div class="topic-card-title">{{ topic.title }}</div>
<el-tag :type="getStatusType(topic.status)" size="small">{{ topic.status }}</el-tag>
</div>
<div class="topic-card-tags">
<el-tag size="small" type="info">{{ topic.field }}</el-tag>
<el-tag size="small" type="warning">合规{{ topic.compliance_score }}</el-tag>
</div>
<div class="topic-card-meta">
<div>创建: {{ formatDate(topic.created_at) }}</div>
<div>创作: {{ topic.generated_at ? formatDate(topic.generated_at) : '-' }}</div>
<div>发布: {{ topic.published_at ? formatDate(topic.published_at) : '-' }}</div>
</div>
<div class="topic-card-actions">
<el-button size="small" @click="openPreview(topic)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="topic.status !== '待处理'" @click="createTopic(topic)">创作</el-button>
<el-button size="small" type="warning" :disabled="topic.status !== '待审查'" @click="optimizeTopic(topic)">审查</el-button>
<el-button v-if="topic.status === '待发布'" size="small" type="primary" @click="handlePublish(topic)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<nav class="mobile-nav">
<button class="mobile-nav-btn" @click="redirectToPage('/')">📊 概览</button>
<button class="mobile-nav-btn active">📋 选题</button>
<button class="mobile-nav-btn" @click="redirectToPage('logs.html')">📄 日志</button>
<button v-if="isAdmin" class="mobile-nav-btn" @click="redirectToPage('users.html')">👥 用户</button>
</nav>
</div>
<script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script>
<script>
const TopicsApp = {
data() {
return {
isLoggedIn: !!localStorage.getItem('authToken'),
isAdmin: false,
currentUser: { username: '' },
loadingTable: false,
selectedTopicIds: [],
filterStatus: '',
topics: [],
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 }
};
},
computed: {
filteredTopics() {
if (!this.topics || !this.topics.length) { return []; }
if (!this.filterStatus) { return this.topics; }
return this.topics.filter(t => t.status === this.filterStatus);
},
countByStatus() {
return (status) => this.topics.filter(t => t.status === status).length;
}
},
methods: {
async fetchTopics() {
this.loadingTable = true;
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/topics/?size=100', { headers: { 'Authorization': 'Bearer ' + token } });
if (!response.ok) throw new Error('获取失败');
const data = await response.json();
this.topics = data || [];
this.loadingTable = false;
} catch (error) {
console.log('获取选题失败,使用模拟数据');
this.$message.error('获取选题失败,使用模拟数据');
this.topics = [
{ id: 'A01', title: '可持续发展趋势分析', field: '环保', status: '待处理', compliance_score: 85, created_at: '2026-04-27 10:30', generated_at: null, published_at: null },
{ id: 'B02', title: 'AI 在内容创作中的应用', field: '科技', status: '待审查', compliance_score: 92, created_at: '2026-04-27 11:15', generated_at: '2026-04-27 11:45', published_at: null },
{ id: 'C03', title: '数字化转型案例研究', field: '商业', status: '待发布', compliance_score: 78, created_at: '2026-04-27 12:00', generated_at: '2026-04-27 12:30', published_at: null }
];
this.loadingTable = false;
}
},
async fetchStats() {
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/system/status', { headers: { 'Authorization': 'Bearer ' + token } });
if (response.ok) {
const data = await response.json();
this.stats = {
total: data.total_topics || 0,
pending: data.topics_by_status ? data.topics_by_status['待处理'] || 0 : 0,
review: data.topics_by_status ? data.topics_by_status['待审查'] || 0 : 0,
ready: data.topics_by_status ? data.topics_by_status['待发布'] || 0 : 0,
published: data.published_count || 0,
today: data.today_articles || 0
};
}
} catch (error) {
console.log('获取统计信息失败');
}
},
refreshAll() { this.$message.info('执行批量刷新'); },
async triggerGenerateSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量创作已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async triggerOptimizeSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量优化已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async createTopic(topic) {
if (topic.status !== '待处理') {
this.$message.info('仅待处理选题可创作');
return;
}
try {
await fetch(`/api/topics/${topic.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ status: '待审查' })
});
this.$message.success('创作完成,状态变更为待审查');
await this.fetchTopics();
} catch (e) {
this.$message.error('创作失败');
}
},
async optimizeTopic(topic) {
if (topic.status !== '待审查') {
this.$message.info('仅待审查选题可优化');
return;
}
try {
await fetch(`/api/topics/${topic.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ status: '待发布' })
});
this.$message.success('优化完成,状态变更为待发布');
await this.fetchTopics();
} catch (e) {
this.$message.error('优化失败');
}
},
async handlePublish(topic) {
if (topic.status !== '待发布') {
this.$message.info('仅待发布选题可发布');
return;
}
try {
const response = await fetch('/api/publishing/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ topic_id: topic.id })
});
if (response.ok) {
const data = await response.json();
this.$message.success('发布成功');
if (data.urls) {
const msg = Object.entries(data.urls).map(([k,v]) => `${k}: ${v}`).join('\n');
this.$notify({ title: '发布链接', message: `<pre>${msg}</pre>`, type: 'success', duration: 0 });
}
} else {
throw new Error('发布失败');
}
await this.fetchTopics();
} catch (e) {
this.$message.error('发布失败');
}
},
deleteTopic(id) {
this.$confirm('确定删除?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => {
try {
const response = await fetch(`/api/topics/${id}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('authToken') }
});
if (response.ok) {
this.$message.success('删除成功');
await this.fetchTopics();
} else {
const err = await response.json().catch(() => ({}));
this.$message.error('删除失败: ' + (err.detail || response.statusText));
}
} catch (e) {
this.$message.error('删除请求失败');
}
})
.catch(() => {});
},
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
openPreview(topic) {
const lines = [
`选题ID: ${topic.id}`,
`标题: ${topic.title}`,
`领域: ${topic.field || '-'}`,
`优先级: ${topic.priority_score}`,
`状态: ${topic.status}`,
`合规分: ${topic.compliance_score || '-'}`,
`创建时间: ${topic.created_at || '-'}`,
`发布时间: ${topic.published_at || '-'}`,
`生成时间: ${topic.generated_at || '-'}`
];
const platform = topic.platform_urls && Object.entries(topic.platform_urls).map(([k, v]) => `${k}: ${v}`).join(', ');
lines.push(`发布平台: ${platform || '未发布'}`);
this.$alert(lines.join('\n'), '选题预览', { width: '600px', customClass: 'preview-dialog' });
},
redirectToPage(page) {
let url = page;
if (page === 'overview' || page === '/') url = '/';
else if (!page.endsWith('.html')) url = page + '.html';
window.location.href = url;
},
formatDate(dateStr) {
if (!dateStr) return '-';
try {
return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
});
} catch (e) { return dateStr; }
},
getStatusType(status) {
const map = { '待处理': 'warning', '待审查': 'danger', '待发布': 'success', '已发布': 'info' };
return map[status] || 'primary';
}
},
mounted() {
console.log('[DEBUG] TopicsApp mounted');
const token = localStorage.getItem('authToken');
console.log('[DEBUG] Token exists:', !!token);
if (!token) { window.location.href = '/'; return; }
const urlParams = new URLSearchParams(window.location.search);
const filter = urlParams.get('filter');
console.log('[DEBUG] URL filter:', filter);
if (filter) { this.filterStatus = filter; }
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
.then(response => response.ok ? response.json() : Promise.reject())
.then(data => {
console.log('[DEBUG] Auth success, user:', data.user);
this.currentUser = data.user;
})
.catch(() => { console.log('[DEBUG] Auth failed'); this.currentUser = {}; });
this.fetchTopics();
this.fetchStats();
}
};
TopicsApp = new Vue(TopicsApp);
</script>
</body>
</html>
-446
View File
@@ -1,446 +0,0 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宇之然内容创作平台 - 选题管理</title>
<link rel="stylesheet" href="/static/element-plus/index.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; }
.navbar { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; padding: 16px 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.navbar-content { display: flex; justify-content: space-between; align-items: center; max-width: 1400px; margin: 0 auto; }
.navbar-title { font-size: 20px; font-weight: 600; }
.navbar-user { display: flex; align-items: center; gap: 16px; }
.user-info { display: flex; align-items: center; gap: 8px; }
.avatar { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
.main-content { display: flex; flex: 1; max-width: 1400px; margin: 0 auto; width: 100%; }
.sidebar { width: 180px; background: white; padding: 16px; box-shadow: 2px 0 8px rgba(0,0,0,0.05); }
.sidebar-btn { width: 100%; text-align: left; padding: 12px 16px; border: none; background: transparent; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; }
.sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
.card { background: white; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.status-badge { display: inline-flex; align-items: center; gap: 4px; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
.status-dot.待处理 { background: #E6A23C; }
.status-dot.待审查 { background: #F56C6C; }
.status-dot.待发布 { background: #67C23A; }
.status-dot.已发布 { background: #409EFF; }
.mobile-nav-btn { flex: 1; border: none; background: transparent; padding: 12px; text-align: center; font-size: 12px; color: #606266; cursor: pointer; }
.mobile-nav-btn.active { color: #409eff; font-weight: 600; }
@media (max-width: 768px) {
.mobile-nav { display: flex !important; }
.topic-card-list { display: block; }
.el-table { display: none; }
.sidebar { display: none; }
.content-area { padding: 16px; padding-bottom: 80px; }
}
/* 移动端卡片布局 */
@media (max-width: 768px) {
.el-table { font-size: 12px; display: none; }
.el-table .el-button { padding: 4px 8px; font-size: 11px; min-height: auto; }
.el-table .cell { padding: 0 4px; }
.el-table .el-table__cell { padding: 6px 0; }
.topic-card {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.topic-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.topic-card-title { font-size: 16px; font-weight: 600; color: #303133; flex: 1; margin-right: 8px; }
.topic-card-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.topic-card-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 12px;
}
.topic-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.topic-card-actions .el-button { flex: 1; min-width: 60px; }
}
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
<style>#app[v-cloak] { display: none; }
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
</head>
<body>
<div id="app" v-cloak>
<nav class="navbar">
<div class="navbar-content">
<h1 class="navbar-title">宇之然内容创作平台 - 选题管理</h1>
<div class="navbar-user">
<div class="user-info"><div class="avatar">{{ currentUser.username ? currentUser.username.charAt(0).toUpperCase() : '?' }}</div><span>{{ currentUser.username }}</span></div>
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
</div>
</div>
</nav>
<div class="main-content">
<aside class="sidebar">
<button class="sidebar-btn" @click="redirectToPage('/')">📊 系统概览</button>
<button class="sidebar-btn active">📋 选题管理</button>
<button class="sidebar-btn" @click="redirectToPage('logs.html')">📄 系统日志</button>
<button v-if="isAdmin" class="sidebar-btn" @click="redirectToPage('users.html')">👥 用户管理</button>
</aside>
<main class="content-area">
<div class="card">
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 24px;">📋 选题管理</h2>
<div class="card" style="display: inline-block; min-width: fit-content; padding: 16px; margin-bottom: 24px;">
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<el-button type="primary" size="small" @click="refreshAll">🔄 批量刷新</el-button>
<el-button type="success" size="small" @click="triggerGenerateSelected" :disabled="selectedTopicIds.length === 0">▶ 批量创作</el-button>
<el-button type="warning" size="small" @click="triggerOptimizeSelected" :disabled="selectedTopicIds.length === 0">🔍 批量优化</el-button>
<span class="ml-auto text-sm text-gray-500" v-if="selectedTopicIds.length > 0" style="color: #909399; font-size: 14px; margin-left: auto;">已选 {{ selectedTopicIds.length }} 项</span>
</div>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap;">
<el-tag size="large" :type="filterStatus === '' ? 'primary' : ''" @click="filterStatus = ''">全部 ({{ topics.length }})</el-tag>
<el-tag size="large" :type="filterStatus === '待处理' ? 'primary' : ''" @click="filterStatus = '待处理'">待处理 ({{ countByStatus('待处理') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待审查' ? 'primary' : ''" @click="filterStatus = '待审查'">待审查 ({{ countByStatus('待审查') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待发布' ? 'primary' : ''" @click="filterStatus = '待发布'">待发布 ({{ countByStatus('待发布') }})</el-tag>
<el-tag size="large" :type="filterStatus === '已发布' ? 'primary' : ''" @click="filterStatus = '已发布'">已发布 ({{ countByStatus('已发布') }})</el-tag>
</div>
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="70" fixed></el-table-column>
<el-table-column prop="title" label="标题" min-width="200"></el-table-column>
<el-table-column prop="field" label="领域" width="100"></el-table-column>
<el-table-column prop="status" label="状态" width="90">
<template #default="scope"><span class="status-badge"><span class="status-dot" :class="scope.row.status"></span>{{ scope.row.status }}</span></template>
</el-table-column>
<el-table-column prop="compliance_score" label="合规分" width="90">
<template #default="scope"><el-progress :percentage="scope.row.compliance_score || 0" :format="() => scope.row.compliance_score || '-'" :stroke-width="15"></el-progress></template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="140"><template #default="scope">{{ formatDate(scope.row.created_at) }}</template></el-table-column>
<el-table-column prop="generated_at" label="创作时间" width="140"><template #default="scope">{{ scope.row.generated_at ? formatDate(scope.row.generated_at) : '-' }}</template></el-table-column>
<el-table-column prop="published_at" label="发布时间" width="140"><template #default="scope">{{ scope.row.published_at ? formatDate(scope.row.published_at) : '-' }}</template></el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
<el-button size="small" @click="openPreview(scope.row)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="scope.row.status !== '待处理'" @click="createTopic(scope.row)">创作</el-button>
<el-button size="small" type="warning" :disabled="scope.row.status !== '待审查'" @click="optimizeTopic(scope.row)">审查</el-button>
<el-button v-if="scope.row.status === '待发布'" size="small" type="primary" @click="handlePublish(scope.row)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 移动端卡片列表 -->
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card">
<div class="topic-card-header">
<div class="topic-card-title">{{ topic.title }}</div>
<el-tag :type="getStatusType(topic.status)" size="small">{{ topic.status }}</el-tag>
</div>
<div class="topic-card-tags">
<el-tag size="small" type="info">{{ topic.field }}</el-tag>
<el-tag size="small" type="warning">合规{{ topic.compliance_score }}</el-tag>
</div>
<div class="topic-card-meta">
<div>创建: {{ formatDate(topic.created_at) }}</div>
<div>创作: {{ topic.generated_at ? formatDate(topic.generated_at) : '-' }}</div>
<div>发布: {{ topic.published_at ? formatDate(topic.published_at) : '-' }}</div>
</div>
<div class="topic-card-actions">
<el-button size="small" @click="openPreview(topic)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="topic.status !== '待处理'" @click="createTopic(topic)">创作</el-button>
<el-button size="small" type="warning" :disabled="topic.status !== '待审查'" @click="optimizeTopic(topic)">审查</el-button>
<el-button v-if="topic.status === '待发布'" size="small" type="primary" @click="handlePublish(topic)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<nav class="mobile-nav">
<button class="mobile-nav-btn" @click="redirectToPage('/')">📊 概览</button>
<button class="mobile-nav-btn active">📋 选题</button>
<button class="mobile-nav-btn" @click="redirectToPage('logs.html')">📄 日志</button>
<button v-if="isAdmin" class="mobile-nav-btn" @click="redirectToPage('users.html')">👥 用户</button>
</nav>
</div>
<script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script>
<script>
const TopicsApp = {
data() {
// 从 localStorage 恢复登录状态
let isLoggedIn = false, isAdmin = false, currentUser = { username: '' };
try {
const userInfo = JSON.parse(localStorage.getItem('user_info'));
if (userInfo) {
isLoggedIn = true;
isAdmin = userInfo.role === 'admin';
currentUser = userInfo;
}
} catch (e) {}
return {
currentPage: 'topics',
isLoggedIn, isAdmin, currentUser,
loadingTable: false,
selectedTopicIds: [],
filterStatus: '',
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
topics: []
};
},
computed: {
filteredTopics() {
if (!this.topics || !this.topics.length) { return []; }
if (!this.filterStatus) { return this.topics; }
return this.topics.filter(t => t.status === this.filterStatus);
},
countByStatus() {
return (status) => this.topics.filter(t => t.status === status).length;
}
},
methods: {
async fetchTopics() {
this.loadingTable = true;
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/topics/?size=100', { headers: { 'Authorization': 'Bearer ' + token } });
if (!response.ok) throw new Error('获取失败');
const data = await response.json();
this.topics = data || [];
this.loadingTable = false;
} catch (error) {
console.log('获取选题失败,使用模拟数据');
this.$message.error('获取选题失败,使用模拟数据');
this.topics = [
{ id: 'A01', title: '可持续发展趋势分析', field: '环保', status: '待处理', compliance_score: 85, created_at: '2026-04-27 10:30', generated_at: null, published_at: null },
{ id: 'B02', title: 'AI 在内容创作中的应用', field: '科技', status: '待审查', compliance_score: 92, created_at: '2026-04-27 11:15', generated_at: '2026-04-27 11:45', published_at: null },
{ id: 'C03', title: '数字化转型案例研究', field: '商业', status: '待发布', compliance_score: 78, created_at: '2026-04-27 12:00', generated_at: '2026-04-27 12:30', published_at: null }
];
this.loadingTable = false;
}
},
async fetchStats() {
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/system/status', { headers: { 'Authorization': 'Bearer ' + token } });
if (response.ok) {
const data = await response.json();
this.stats = {
total: data.total_topics || 0,
pending: data.topics_by_status ? data.topics_by_status['待处理'] || 0 : 0,
review: data.topics_by_status ? data.topics_by_status['待审查'] || 0 : 0,
ready: data.topics_by_status ? data.topics_by_status['待发布'] || 0 : 0,
published: data.published_count || 0,
today: data.today_articles || 0
};
}
} catch (error) {
console.log('获取统计信息失败');
}
},
refreshAll() { this.$message.info('执行批量刷新'); },
async triggerGenerateSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量创作已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async triggerOptimizeSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量优化已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async createTopic(topic) {
if (topic.status !== '待处理') {
this.$message.info('仅待处理选题可创作');
return;
}
try {
await fetch(`/api/topics/${topic.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ status: '待审查' })
});
this.$message.success('创作完成,状态变更为待审查');
await this.fetchTopics();
} catch (e) {
this.$message.error('创作失败');
}
},
async optimizeTopic(topic) {
if (topic.status !== '待审查') {
this.$message.info('仅待审查选题可优化');
return;
}
try {
await fetch(`/api/topics/${topic.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ status: '待发布' })
});
this.$message.success('优化完成,状态变更为待发布');
await this.fetchTopics();
} catch (e) {
this.$message.error('优化失败');
}
},
async handlePublish(topic) {
if (topic.status !== '待发布') {
this.$message.info('仅待发布选题可发布');
return;
}
try {
const response = await fetch('/api/publishing/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ topic_id: topic.id })
});
if (response.ok) {
const data = await response.json();
this.$message.success('发布成功');
if (data.urls) {
');
this.$notify({ title: '发布链接', message: `<pre>${msg}</pre>`, type: 'success', duration: 0 });
}
} else {
throw new Error('发布失败');
}
await this.fetchTopics();
} catch (e) {
this.$message.error('发布失败');
}
},
deleteTopic(id) {
this.$confirm('确定删除?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => {
try {
const response = await fetch(`/api/topics/${id}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('authToken') }
});
if (response.ok) {
this.$message.success('删除成功');
await this.fetchTopics();
} else {
const err = await response.json().catch(() => ({}));
this.$message.error('删除失败: ' + (err.detail || response.statusText));
}
} catch (e) {
this.$message.error('删除请求失败');
}
})
.catch(() => {});
},
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
openPreview(topic) {
const lines = [
`选题ID: ${topic.id}`,
`标题: ${topic.title}`,
`领域: ${topic.field || '-'}`,
`优先级: ${topic.priority_score}`,
`状态: ${topic.status}`,
`合规分: ${topic.compliance_score || '-'}`,
`创建时间: ${topic.created_at || '-'}`,
`发布时间: ${topic.published_at || '-'}`,
`生成时间: ${topic.generated_at || '-'}`
];
const platform = topic.platform_urls && Object.entries(topic.platform_urls).map(([k, v]) => `${k}: ${v}`).join(', ');
lines.push(`发布平台: ${platform || '未发布'}`);
this.$alert(lines.join('
'), '选题预览', { width: '600px', customClass: 'preview-dialog' });
},
redirectToPage(page) {
let url = page;
if (page === 'overview' || page === '/') url = '/';
else if (!page.endsWith('.html')) url = page + '.html';
window.location.href = url;
},
formatDate(dateStr) {
if (!dateStr) return '-';
try {
return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
});
} catch (e) { return dateStr; }
},
getStatusType(status) {
const map = { '待处理': 'warning', '待审查': 'danger', '待发布': 'success', '已发布': 'info' };
return map[status] || 'primary';
}
},
mounted() {
console.log('[DEBUG] TopicsApp mounted');
const token = localStorage.getItem('authToken');
console.log('[DEBUG] Token exists:', !!token);
if (!token) { window.location.href = '/'; return; }
// 解析 URL filter 参数
const urlParams = new URLSearchParams(window.location.search);
const filter = urlParams.get('filter');
console.log('[DEBUG] URL filter:', filter);
if (filter) { this.filterStatus = filter; }
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
.then(response => response.ok ? response.json() : Promise.reject())
.then(data => {
console.log('[DEBUG] Auth success, user:', data.user);
this.currentUser = data.user;
this.isAdmin = data.user.role === 'admin';
this.isLoggedIn = true;
this.fetchTopics();
})
.catch((err) => {
console.error('[DEBUG] Auth failed in topics.html', err);
localStorage.removeItem('authToken');
window.location.href = '/';
});
}
};
const app = Vue.createApp(TopicsApp);
app.use(ElementPlus);
app.mount('#app');
</script>
</body>
</html>
-430
View File
@@ -1,430 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宇之然内容创作平台 - 选题管理</title>
<link rel="stylesheet" href="/static/element-plus/index.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; }
.navbar { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; padding: 16px 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.navbar-content { display: flex; justify-content: space-between; align-items: center; max-width: 1400px; margin: 0 auto; }
.navbar-title { font-size: 20px; font-weight: 600; }
.navbar-user { display: flex; align-items: center; gap: 16px; }
.user-info { display: flex; align-items: center; gap: 8px; }
.avatar { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
.main-content { display: flex; flex: 1; max-width: 1400px; margin: 0 auto; width: 100%; }
.sidebar { width: 180px; background: white; padding: 16px; box-shadow: 2px 0 8px rgba(0,0,0,0.05); }
.sidebar-btn { width: 100%; text-align: left; padding: 12px 16px; border: none; background: transparent; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; }
.sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
.card { background: white; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.status-badge { display: inline-flex; align-items: center; gap: 4px; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
.status-dot.待处理 { background: #E6A23C; }
.status-dot.待审查 { background: #F56C6C; }
.status-dot.待发布 { background: #67C23A; }
.status-dot.已发布 { background: #409EFF; }
.mobile-nav-btn { flex: 1; border: none; background: transparent; padding: 12px; text-align: center; font-size: 12px; color: #606266; cursor: pointer; }
.mobile-nav-btn.active { color: #409eff; font-weight: 600; }
@media (max-width: 768px) {
.mobile-nav { display: flex !important; }
.topic-card-list { display: block; }
.el-table { display: none; }
.sidebar { display: none; }
.content-area { padding: 16px; padding-bottom: 80px; }
}
/* 移动端卡片布局 */
@media (max-width: 768px) {
.el-table { font-size: 12px; display: none; }
.el-table .el-button { padding: 4px 8px; font-size: 11px; min-height: auto; }
.el-table .cell { padding: 0 4px; }
.el-table .el-table__cell { padding: 6px 0; }
.topic-card {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.topic-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.topic-card-title { font-size: 16px; font-weight: 600; color: #303133; flex: 1; margin-right: 8px; }
.topic-card-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.topic-card-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 12px;
}
.topic-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.topic-card-actions .el-button { flex: 1; min-width: 60px; }
}
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
<style>#app[v-cloak] { display: none; }
/* 桌面端默认:显示表格,隐藏移动端元素和卡片 */
.mobile-nav { display: none !important; }
.topic-card-list { display: none; }
.el-table { display: table; }
</style>
</head>
<body>
<div id="app" v-cloak>
<nav class="navbar">
<div class="navbar-content">
<h1 class="navbar-title">宇之然内容创作平台 - 选题管理</h1>
<div class="navbar-user">
<div class="user-info"><div class="avatar">{{ currentUser.username ? currentUser.username.charAt(0).toUpperCase() : '?' }}</div><span>{{ currentUser.username }}</span></div>
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
</div>
</div>
</nav>
<div class="main-content">
<aside class="sidebar">
<button class="sidebar-btn" @click="redirectToPage('/')">📊 系统概览</button>
<button class="sidebar-btn active">📋 选题管理</button>
<button class="sidebar-btn" @click="redirectToPage('logs.html')">📄 系统日志</button>
<button v-if="isAdmin" class="sidebar-btn" @click="redirectToPage('users.html')">👥 用户管理</button>
</aside>
<main class="content-area">
<div class="card">
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 24px;">📋 选题管理</h2>
<div class="card" style="display: inline-block; min-width: fit-content; padding: 16px; margin-bottom: 24px;">
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<el-button type="primary" size="small" @click="refreshAll">🔄 批量刷新</el-button>
<el-button type="success" size="small" @click="triggerGenerateSelected" :disabled="selectedTopicIds.length === 0">▶ 批量创作</el-button>
<el-button type="warning" size="small" @click="triggerOptimizeSelected" :disabled="selectedTopicIds.length === 0">🔍 批量优化</el-button>
<span class="ml-auto text-sm text-gray-500" v-if="selectedTopicIds.length > 0" style="color: #909399; font-size: 14px; margin-left: auto;">已选 {{ selectedTopicIds.length }} 项</span>
</div>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap;">
<el-tag size="large" :type="filterStatus === '' ? 'primary' : ''" @click="filterStatus = ''">全部 ({{ topics.length }})</el-tag>
<el-tag size="large" :type="filterStatus === '待处理' ? 'primary' : ''" @click="filterStatus = '待处理'">待处理 ({{ countByStatus('待处理') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待审查' ? 'primary' : ''" @click="filterStatus = '待审查'">待审查 ({{ countByStatus('待审查') }})</el-tag>
<el-tag size="large" :type="filterStatus === '待发布' ? 'primary' : ''" @click="filterStatus = '待发布'">待发布 ({{ countByStatus('待发布') }})</el-tag>
<el-tag size="large" :type="filterStatus === '已发布' ? 'primary' : ''" @click="filterStatus = '已发布'">已发布 ({{ countByStatus('已发布') }})</el-tag>
</div>
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="70" fixed></el-table-column>
<el-table-column prop="title" label="标题" min-width="200"></el-table-column>
<el-table-column prop="field" label="领域" width="100"></el-table-column>
<el-table-column prop="status" label="状态" width="90">
<template #default="scope"><span class="status-badge"><span class="status-dot" :class="scope.row.status"></span>{{ scope.row.status }}</span></template>
</el-table-column>
<el-table-column prop="compliance_score" label="合规分" width="90">
<template #default="scope"><el-progress :percentage="scope.row.compliance_score || 0" :format="() => scope.row.compliance_score || '-'" :stroke-width="15"></el-progress></template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="140"><template #default="scope">{{ formatDate(scope.row.created_at) }}</template></el-table-column>
<el-table-column prop="generated_at" label="创作时间" width="140"><template #default="scope">{{ scope.row.generated_at ? formatDate(scope.row.generated_at) : '-' }}</template></el-table-column>
<el-table-column prop="published_at" label="发布时间" width="140"><template #default="scope">{{ scope.row.published_at ? formatDate(scope.row.published_at) : '-' }}</template></el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
<el-button size="small" @click="openPreview(scope.row)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="scope.row.status !== '待处理'" @click="createTopic(scope.row)">创作</el-button>
<el-button size="small" type="warning" :disabled="scope.row.status !== '待审查'" @click="optimizeTopic(scope.row)">审查</el-button>
<el-button v-if="scope.row.status === '待发布'" size="small" type="primary" @click="handlePublish(scope.row)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 移动端卡片列表 -->
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card">
<div class="topic-card-header">
<div class="topic-card-title">{{ topic.title }}</div>
<el-tag :type="getStatusType(topic.status)" size="small">{{ topic.status }}</el-tag>
</div>
<div class="topic-card-tags">
<el-tag size="small" type="info">{{ topic.field }}</el-tag>
<el-tag size="small" type="warning">合规{{ topic.compliance_score }}</el-tag>
</div>
<div class="topic-card-meta">
<div>创建: {{ formatDate(topic.created_at) }}</div>
<div>创作: {{ topic.generated_at ? formatDate(topic.generated_at) : '-' }}</div>
<div>发布: {{ topic.published_at ? formatDate(topic.published_at) : '-' }}</div>
</div>
<div class="topic-card-actions">
<el-button size="small" @click="openPreview(topic)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="topic.status !== '待处理'" @click="createTopic(topic)">创作</el-button>
<el-button size="small" type="warning" :disabled="topic.status !== '待审查'" @click="optimizeTopic(topic)">审查</el-button>
<el-button v-if="topic.status === '待发布'" size="small" type="primary" @click="handlePublish(topic)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<nav class="mobile-nav">
<button class="mobile-nav-btn" @click="redirectToPage('/')">📊 概览</button>
<button class="mobile-nav-btn active">📋 选题</button>
<button class="mobile-nav-btn" @click="redirectToPage('logs.html')">📄 日志</button>
<button v-if="isAdmin" class="mobile-nav-btn" @click="redirectToPage('users.html')">👥 用户</button>
</nav>
</div>
<script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script>
<script>
const TopicsApp = {
data() {
return {
isLoggedIn: !!localStorage.getItem('authToken'),
isAdmin: false,
currentUser: { username: '' },
loadingTable: false,
selectedTopicIds: [],
filterStatus: '',
topics: [],
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 }
};
},
computed: {
filteredTopics() {
if (!this.topics || !this.topics.length) { return []; }
if (!this.filterStatus) { return this.topics; }
return this.topics.filter(t => t.status === this.filterStatus);
},
countByStatus() {
return (status) => this.topics.filter(t => t.status === status).length;
}
},
methods: {
async fetchTopics() {
this.loadingTable = true;
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/topics/?size=100', { headers: { 'Authorization': 'Bearer ' + token } });
if (!response.ok) throw new Error('获取失败');
const data = await response.json();
this.topics = data || [];
this.loadingTable = false;
} catch (error) {
console.log('获取选题失败,使用模拟数据');
this.$message.error('获取选题失败,使用模拟数据');
this.topics = [
{ id: 'A01', title: '可持续发展趋势分析', field: '环保', status: '待处理', compliance_score: 85, created_at: '2026-04-27 10:30', generated_at: null, published_at: null },
{ id: 'B02', title: 'AI 在内容创作中的应用', field: '科技', status: '待审查', compliance_score: 92, created_at: '2026-04-27 11:15', generated_at: '2026-04-27 11:45', published_at: null },
{ id: 'C03', title: '数字化转型案例研究', field: '商业', status: '待发布', compliance_score: 78, created_at: '2026-04-27 12:00', generated_at: '2026-04-27 12:30', published_at: null }
];
this.loadingTable = false;
}
},
async fetchStats() {
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/system/status', { headers: { 'Authorization': 'Bearer ' + token } });
if (response.ok) {
const data = await response.json();
this.stats = {
total: data.total_topics || 0,
pending: data.topics_by_status ? data.topics_by_status['待处理'] || 0 : 0,
review: data.topics_by_status ? data.topics_by_status['待审查'] || 0 : 0,
ready: data.topics_by_status ? data.topics_by_status['待发布'] || 0 : 0,
published: data.published_count || 0,
today: data.today_articles || 0
};
}
} catch (error) {
console.log('获取统计信息失败');
}
},
refreshAll() { this.$message.info('执行批量刷新'); },
async triggerGenerateSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量创作已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async triggerOptimizeSelected() {
if (!this.selectedTopicIds.length) return;
this.$message.success('批量优化已启动');
this.selectedTopicIds = [];
await this.fetchTopics();
},
async createTopic(topic) {
if (topic.status !== '待处理') {
this.$message.info('仅待处理选题可创作');
return;
}
try {
await fetch(`/api/topics/${topic.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ status: '待审查' })
});
this.$message.success('创作完成,状态变更为待审查');
await this.fetchTopics();
} catch (e) {
this.$message.error('创作失败');
}
},
async optimizeTopic(topic) {
if (topic.status !== '待审查') {
this.$message.info('仅待审查选题可优化');
return;
}
try {
await fetch(`/api/topics/${topic.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ status: '待发布' })
});
this.$message.success('优化完成,状态变更为待发布');
await this.fetchTopics();
} catch (e) {
this.$message.error('优化失败');
}
},
async handlePublish(topic) {
if (topic.status !== '待发布') {
this.$message.info('仅待发布选题可发布');
return;
}
try {
const response = await fetch('/api/publishing/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({ topic_id: topic.id })
});
if (response.ok) {
const data = await response.json();
this.$message.success('发布成功');
if (data.urls) {
const msg = Object.entries(data.urls).map(([k,v]) => `${k}: ${v}`).join('\n');
this.$notify({ title: '发布链接', message: `<pre>${msg}</pre>`, type: 'success', duration: 0 });
}
} else {
throw new Error('发布失败');
}
await this.fetchTopics();
} catch (e) {
this.$message.error('发布失败');
}
},
deleteTopic(id) {
this.$confirm('确定删除?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => {
try {
const response = await fetch(`/api/topics/${id}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('authToken') }
});
if (response.ok) {
this.$message.success('删除成功');
await this.fetchTopics();
} else {
const err = await response.json().catch(() => ({}));
this.$message.error('删除失败: ' + (err.detail || response.statusText));
}
} catch (e) {
this.$message.error('删除请求失败');
}
})
.catch(() => {});
},
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
openPreview(topic) {
const lines = [
`选题ID: ${topic.id}`,
`标题: ${topic.title}`,
`领域: ${topic.field || '-'}`,
`优先级: ${topic.priority_score}`,
`状态: ${topic.status}`,
`合规分: ${topic.compliance_score || '-'}`,
`创建时间: ${topic.created_at || '-'}`,
`发布时间: ${topic.published_at || '-'}`,
`生成时间: ${topic.generated_at || '-'}`
];
const platform = topic.platform_urls && Object.entries(topic.platform_urls).map(([k, v]) => `${k}: ${v}`).join(', ');
lines.push(`发布平台: ${platform || '未发布'}`);
this.$alert(lines.join('\n'), '选题预览', { width: '600px', customClass: 'preview-dialog' });
},
redirectToPage(page) {
let url = page;
if (page === 'overview' || page === '/') url = '/';
else if (!page.endsWith('.html')) url = page + '.html';
window.location.href = url;
},
formatDate(dateStr) {
if (!dateStr) return '-';
try {
return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
});
} catch (e) { return dateStr; }
},
getStatusType(status) {
const map = { '待处理': 'warning', '待审查': 'danger', '待发布': 'success', '已发布': 'info' };
return map[status] || 'primary';
}
},
mounted() {
console.log('[DEBUG] TopicsApp mounted');
const token = localStorage.getItem('authToken');
console.log('[DEBUG] Token exists:', !!token);
if (!token) { window.location.href = '/'; return; }
const urlParams = new URLSearchParams(window.location.search);
const filter = urlParams.get('filter');
console.log('[DEBUG] URL filter:', filter);
if (filter) { this.filterStatus = filter; }
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
.then(response => response.ok ? response.json() : Promise.reject())
.then(data => {
console.log('[DEBUG] Auth success, user:', data.user);
this.currentUser = data.user;
})
.catch(() => { console.log('[DEBUG] Auth failed'); this.currentUser = {}; });
this.fetchTopics();
this.fetchStats();
}
};
TopicsApp = new Vue(TopicsApp);
</script>
</body>
</html>
+68 -14
View File
@@ -123,31 +123,85 @@
async fetchUsers() {
try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/admin/users', { headers: { 'Authorization': 'Bearer ' + token } });
if (!response.ok) throw new Error('获取失败');
if (!token) {
this.$message.error('请先登录');
return;
}
const response = await fetch('/api/admin/users', {
headers: { 'Authorization': 'Bearer ' + token }
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
const data = await response.json();
this.users = data || [];
this.$message.success('用户列表加载成功');
} catch (error) {
console.log('使用模拟数据');
this.users = [
{ id: 'admin', username: '管理员', role: 'admin', created_at: '2026-04-01 09:00' },
{ id: 'editor1', username: '编辑小王', role: 'editor', created_at: '2026-04-05 14:30' },
{ id: 'editor2', username: '编辑小李', role: 'editor', created_at: '2026-04-10 10:15' }
];
console.error('获取用户失败:', error);
this.$message.error(`获取用户失败: ${error.message}`);
this.users = [];
}
},
addUser() {
const newId = 'user' + Date.now();
this.users.push({ id: newId, username: '新用户', role: 'editor', created_at: new Date().toISOString().slice(0, 16).replace('T', ' ') });
this.$message.success('添加用户成功');
async addUser() {
try {
const token = localStorage.getItem('authToken');
if (!token) {
this.$message.error('请先登录');
return;
}
// 使用默认用户名,添加时间戳避免重复
const username = '新用户' + Date.now().toString().slice(-4);
const response = await fetch('/api/admin/users', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ username: username, role: 'editor' })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
const newUser = await response.json();
this.users.push(newUser);
this.$message.success('添加用户成功');
} catch (error) {
console.error('添加用户失败:', error);
this.$message.error(`添加用户失败: ${error.message}`);
}
},
deleteUser(id) {
async deleteUser(id) {
if (id === 'admin') {
this.$message.warning('不能删除管理员用户');
return;
}
this.$confirm('确定删除该用户?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(() => { this.users = this.users.filter(u => u.id !== id); this.$message.success('删除用户成功'); }).catch(() => {});
.then(async () => {
try {
const token = localStorage.getItem('authToken');
if (!token) {
this.$message.error('请先登录');
window.location.href = '/';
return;
}
const response = await fetch(`/api/admin/users/${encodeURIComponent(id)}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + token }
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `请求失败: ${response.status}`);
}
this.users = this.users.filter(u => u.id !== id);
this.$message.success('删除用户成功');
} catch (error) {
console.error('删除用户失败:', error);
this.$message.error(`删除用户失败: ${error.message}`);
}
})
.catch(() => {});
},
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
redirectToPage(page) { window.location.href = '/' + page; },
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+87
View File
@@ -0,0 +1,87 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId']
const useLink: typeof import('vue-router')['useLink']
const useModel: typeof import('vue')['useModel']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
+35
View File
@@ -0,0 +1,35 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElTree: typeof import('element-plus/es')['ElTree']
Footer: typeof import('./src/components/Footer.vue')['default']
MobileNav: typeof import('./src/components/MobileNav.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Sidebar: typeof import('./src/components/Sidebar.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>宇之然内容创作平台</title>
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
../acorn/bin/acorn
+1
View File
@@ -0,0 +1 @@
../esbuild/bin/esbuild
+1
View File
@@ -0,0 +1 @@
../nanoid/bin/nanoid.cjs
+1
View File
@@ -0,0 +1 @@
../@babel/parser/bin/babel-parser.js
+1
View File
@@ -0,0 +1 @@
../resolve/bin/resolve
+1
View File
@@ -0,0 +1 @@
../rollup/dist/bin/rollup
+1
View File
@@ -0,0 +1 @@
../vite/bin/vite.js
+1
View File
@@ -0,0 +1 @@
../vue-demi/bin/vue-demi-fix.js
+1
View File
@@ -0,0 +1 @@
../vue-demi/bin/vue-demi-switch.js
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Anthony Fu <https://github.com/antfu>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+24
View File
@@ -0,0 +1,24 @@
# @antfu/utils
[![NPM version](https://img.shields.io/npm/v/@antfu/utils?color=a1b858&label=)](https://www.npmjs.com/package/@antfu/utils)
[![Docs](https://img.shields.io/badge/docs-green)](https://www.jsdocs.io/package/@antfu/utils)
Opinionated collection of common JavaScript / TypeScript utils by [@antfu](https://github.com/antfu).
- Tree-shakable ESM
- Fully typed - with TSDocs
- Type utilities - `Arrayable<T>`, `ElementOf<T>`, etc.
> This package is designed to be used as `devDependencies` and bundled into your dist.
## Sponsors
<p align="center">
<a href="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg">
<img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg'/>
</a>
</p>
## License
[MIT](./LICENSE) License © 2021-PRESENT [Anthony Fu](https://github.com/antfu)
+844
View File
@@ -0,0 +1,844 @@
'use strict';
function clamp(n, min, max) {
return Math.min(max, Math.max(min, n));
}
function sum(...args) {
return flattenArrayable(args).reduce((a, b) => a + b, 0);
}
function lerp(min, max, t) {
const interpolation = clamp(t, 0, 1);
return min + (max - min) * interpolation;
}
function remap(n, inMin, inMax, outMin, outMax) {
const interpolation = (n - inMin) / (inMax - inMin);
return lerp(outMin, outMax, interpolation);
}
function toArray(array) {
array = array ?? [];
return Array.isArray(array) ? array : [array];
}
function flattenArrayable(array) {
return toArray(array).flat(1);
}
function mergeArrayable(...args) {
return args.flatMap((i) => toArray(i));
}
function partition(array, ...filters) {
const result = Array.from({ length: filters.length + 1 }).fill(null).map(() => []);
array.forEach((e, idx, arr) => {
let i = 0;
for (const filter of filters) {
if (filter(e, idx, arr)) {
result[i].push(e);
return;
}
i += 1;
}
result[i].push(e);
});
return result;
}
function uniq(array) {
return Array.from(new Set(array));
}
function uniqueBy(array, equalFn) {
return array.reduce((acc, cur) => {
const index = acc.findIndex((item) => equalFn(cur, item));
if (index === -1)
acc.push(cur);
return acc;
}, []);
}
function last(array) {
return at(array, -1);
}
function remove(array, value) {
if (!array)
return false;
const index = array.indexOf(value);
if (index >= 0) {
array.splice(index, 1);
return true;
}
return false;
}
function at(array, index) {
const len = array.length;
if (!len)
return void 0;
if (index < 0)
index += len;
return array[index];
}
function range(...args) {
let start, stop, step;
if (args.length === 1) {
start = 0;
step = 1;
[stop] = args;
} else {
[start, stop, step = 1] = args;
}
const arr = [];
let current = start;
while (current < stop) {
arr.push(current);
current += step || 1;
}
return arr;
}
function move(arr, from, to) {
arr.splice(to, 0, arr.splice(from, 1)[0]);
return arr;
}
function clampArrayRange(n, arr) {
return clamp(n, 0, arr.length - 1);
}
function sample(arr, quantity) {
return Array.from({ length: quantity }, (_) => arr[Math.round(Math.random() * (arr.length - 1))]);
}
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function assert(condition, message) {
if (!condition)
throw new Error(message);
}
const toString = (v) => Object.prototype.toString.call(v);
function getTypeName(v) {
if (v === null)
return "null";
const type = toString(v).slice(8, -1).toLowerCase();
return typeof v === "object" || typeof v === "function" ? type : typeof v;
}
function noop() {
}
function isDeepEqual(value1, value2) {
const type1 = getTypeName(value1);
const type2 = getTypeName(value2);
if (type1 !== type2)
return false;
if (type1 === "array") {
if (value1.length !== value2.length)
return false;
return value1.every((item, i) => {
return isDeepEqual(item, value2[i]);
});
}
if (type1 === "object") {
const keyArr = Object.keys(value1);
if (keyArr.length !== Object.keys(value2).length)
return false;
return keyArr.every((key) => {
return isDeepEqual(value1[key], value2[key]);
});
}
return Object.is(value1, value2);
}
function notNullish(v) {
return v != null;
}
function noNull(v) {
return v !== null;
}
function notUndefined(v) {
return v !== void 0;
}
function isTruthy(v) {
return Boolean(v);
}
const isDef = (val) => typeof val !== "undefined";
const isBoolean = (val) => typeof val === "boolean";
const isFunction = (val) => typeof val === "function";
const isNumber = (val) => typeof val === "number";
const isString = (val) => typeof val === "string";
const isObject = (val) => toString(val) === "[object Object]";
const isUndefined = (val) => toString(val) === "[object Undefined]";
const isNull = (val) => toString(val) === "[object Null]";
const isRegExp = (val) => toString(val) === "[object RegExp]";
const isDate = (val) => toString(val) === "[object Date]";
const isWindow = (val) => typeof window !== "undefined" && toString(val) === "[object Window]";
const isBrowser = typeof window !== "undefined";
function slash(str) {
return str.replace(/\\/g, "/");
}
function ensurePrefix(prefix, str) {
if (!str.startsWith(prefix))
return prefix + str;
return str;
}
function ensureSuffix(suffix, str) {
if (!str.endsWith(suffix))
return str + suffix;
return str;
}
function template(str, ...args) {
const [firstArg, fallback] = args;
if (isObject(firstArg)) {
const vars = firstArg;
return str.replace(/{([\w\d]+)}/g, (_, key) => vars[key] || ((typeof fallback === "function" ? fallback(key) : fallback) ?? key));
} else {
return str.replace(/{(\d+)}/g, (_, key) => {
const index = Number(key);
if (Number.isNaN(index))
return key;
return args[index];
});
}
}
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
function randomStr(size = 16, dict = urlAlphabet) {
let id = "";
let i = size;
const len = dict.length;
while (i--)
id += dict[Math.random() * len | 0];
return id;
}
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1).toLowerCase();
}
const _reFullWs = /^\s*$/;
function unindent(str) {
const lines = (typeof str === "string" ? str : str[0]).split("\n");
const whitespaceLines = lines.map((line) => _reFullWs.test(line));
const commonIndent = lines.reduce((min, line, idx) => {
var _a;
if (whitespaceLines[idx])
return min;
const indent = (_a = line.match(/^\s*/)) == null ? void 0 : _a[0].length;
return indent === void 0 ? min : Math.min(min, indent);
}, Number.POSITIVE_INFINITY);
let emptyLinesHead = 0;
while (emptyLinesHead < lines.length && whitespaceLines[emptyLinesHead])
emptyLinesHead++;
let emptyLinesTail = 0;
while (emptyLinesTail < lines.length && whitespaceLines[lines.length - emptyLinesTail - 1])
emptyLinesTail++;
return lines.slice(emptyLinesHead, lines.length - emptyLinesTail).map((line) => line.slice(commonIndent)).join("\n");
}
const timestamp = () => +Date.now();
function batchInvoke(functions) {
functions.forEach((fn) => fn && fn());
}
function invoke(fn) {
return fn();
}
function tap(value, callback) {
callback(value);
return value;
}
function objectMap(obj, fn) {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => fn(k, v)).filter(notNullish)
);
}
function isKeyOf(obj, k) {
return k in obj;
}
function objectKeys(obj) {
return Object.keys(obj);
}
function objectEntries(obj) {
return Object.entries(obj);
}
function deepMerge(target, ...sources) {
if (!sources.length)
return target;
const source = sources.shift();
if (source === void 0)
return target;
if (isMergableObject(target) && isMergableObject(source)) {
objectKeys(source).forEach((key) => {
if (key === "__proto__" || key === "constructor" || key === "prototype")
return;
if (isMergableObject(source[key])) {
if (!target[key])
target[key] = {};
if (isMergableObject(target[key])) {
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
} else {
target[key] = source[key];
}
});
}
return deepMerge(target, ...sources);
}
function deepMergeWithArray(target, ...sources) {
if (!sources.length)
return target;
const source = sources.shift();
if (source === void 0)
return target;
if (Array.isArray(target) && Array.isArray(source))
target.push(...source);
if (isMergableObject(target) && isMergableObject(source)) {
objectKeys(source).forEach((key) => {
if (key === "__proto__" || key === "constructor" || key === "prototype")
return;
if (Array.isArray(source[key])) {
if (!target[key])
target[key] = [];
deepMergeWithArray(target[key], source[key]);
} else if (isMergableObject(source[key])) {
if (!target[key])
target[key] = {};
deepMergeWithArray(target[key], source[key]);
} else {
target[key] = source[key];
}
});
}
return deepMergeWithArray(target, ...sources);
}
function isMergableObject(item) {
return isObject(item) && !Array.isArray(item);
}
function objectPick(obj, keys, omitUndefined = false) {
return keys.reduce((n, k) => {
if (k in obj) {
if (!omitUndefined || obj[k] !== void 0)
n[k] = obj[k];
}
return n;
}, {});
}
function clearUndefined(obj) {
Object.keys(obj).forEach((key) => obj[key] === void 0 ? delete obj[key] : {});
return obj;
}
function hasOwnProperty(obj, v) {
if (obj == null)
return false;
return Object.prototype.hasOwnProperty.call(obj, v);
}
function createSingletonPromise(fn) {
let _promise;
function wrapper() {
if (!_promise)
_promise = fn();
return _promise;
}
wrapper.reset = async () => {
const _prev = _promise;
_promise = void 0;
if (_prev)
await _prev;
};
return wrapper;
}
function sleep(ms, callback) {
return new Promise(
(resolve) => setTimeout(async () => {
await (callback == null ? void 0 : callback());
resolve();
}, ms)
);
}
function createPromiseLock() {
const locks = [];
return {
async run(fn) {
const p = fn();
locks.push(p);
try {
return await p;
} finally {
remove(locks, p);
}
},
async wait() {
await Promise.allSettled(locks);
},
isWaiting() {
return Boolean(locks.length);
},
clear() {
locks.length = 0;
}
};
}
function createControlledPromise() {
let resolve, reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
}
/* eslint-disable no-undefined,no-param-reassign,no-shadow */
/**
* Throttle execution of a function. Especially useful for rate limiting
* execution of handlers on events like resize and scroll.
*
* @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher)
* are most useful.
* @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through,
* as-is, to `callback` when the throttled-function is executed.
* @param {object} [options] - An object to configure options.
* @param {boolean} [options.noTrailing] - Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds
* while the throttled-function is being called. If noTrailing is false or unspecified, callback will be executed
* one final time after the last throttled-function call. (After the throttled-function has not been called for
* `delay` milliseconds, the internal counter is reset).
* @param {boolean} [options.noLeading] - Optional, defaults to false. If noLeading is false, the first throttled-function call will execute callback
* immediately. If noLeading is true, the first the callback execution will be skipped. It should be noted that
* callback will never executed if both noLeading = true and noTrailing = true.
* @param {boolean} [options.debounceMode] - If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is
* false (at end), schedule `callback` to execute after `delay` ms.
*
* @returns {Function} A new, throttled, function.
*/
function throttle (delay, callback, options) {
var _ref = options || {},
_ref$noTrailing = _ref.noTrailing,
noTrailing = _ref$noTrailing === void 0 ? false : _ref$noTrailing,
_ref$noLeading = _ref.noLeading,
noLeading = _ref$noLeading === void 0 ? false : _ref$noLeading,
_ref$debounceMode = _ref.debounceMode,
debounceMode = _ref$debounceMode === void 0 ? undefined : _ref$debounceMode;
/*
* After wrapper has stopped being called, this timeout ensures that
* `callback` is executed at the proper times in `throttle` and `end`
* debounce modes.
*/
var timeoutID;
var cancelled = false; // Keep track of the last time `callback` was executed.
var lastExec = 0; // Function to clear existing timeout
function clearExistingTimeout() {
if (timeoutID) {
clearTimeout(timeoutID);
}
} // Function to cancel next exec
function cancel(options) {
var _ref2 = options || {},
_ref2$upcomingOnly = _ref2.upcomingOnly,
upcomingOnly = _ref2$upcomingOnly === void 0 ? false : _ref2$upcomingOnly;
clearExistingTimeout();
cancelled = !upcomingOnly;
}
/*
* The `wrapper` function encapsulates all of the throttling / debouncing
* functionality and when executed will limit the rate at which `callback`
* is executed.
*/
function wrapper() {
for (var _len = arguments.length, arguments_ = new Array(_len), _key = 0; _key < _len; _key++) {
arguments_[_key] = arguments[_key];
}
var self = this;
var elapsed = Date.now() - lastExec;
if (cancelled) {
return;
} // Execute `callback` and update the `lastExec` timestamp.
function exec() {
lastExec = Date.now();
callback.apply(self, arguments_);
}
/*
* If `debounceMode` is true (at begin) this is used to clear the flag
* to allow future `callback` executions.
*/
function clear() {
timeoutID = undefined;
}
if (!noLeading && debounceMode && !timeoutID) {
/*
* Since `wrapper` is being called for the first time and
* `debounceMode` is true (at begin), execute `callback`
* and noLeading != true.
*/
exec();
}
clearExistingTimeout();
if (debounceMode === undefined && elapsed > delay) {
if (noLeading) {
/*
* In throttle mode with noLeading, if `delay` time has
* been exceeded, update `lastExec` and schedule `callback`
* to execute after `delay` ms.
*/
lastExec = Date.now();
if (!noTrailing) {
timeoutID = setTimeout(debounceMode ? clear : exec, delay);
}
} else {
/*
* In throttle mode without noLeading, if `delay` time has been exceeded, execute
* `callback`.
*/
exec();
}
} else if (noTrailing !== true) {
/*
* In trailing throttle mode, since `delay` time has not been
* exceeded, schedule `callback` to execute `delay` ms after most
* recent execution.
*
* If `debounceMode` is true (at begin), schedule `clear` to execute
* after `delay` ms.
*
* If `debounceMode` is false (at end), schedule `callback` to
* execute after `delay` ms.
*/
timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);
}
}
wrapper.cancel = cancel; // Return the wrapper function.
return wrapper;
}
/* eslint-disable no-undefined */
/**
* Debounce execution of a function. Debouncing, unlike throttling,
* guarantees that a function is only executed a single time, either at the
* very beginning of a series of calls, or at the very end.
*
* @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.
* @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,
* to `callback` when the debounced-function is executed.
* @param {object} [options] - An object to configure options.
* @param {boolean} [options.atBegin] - Optional, defaults to false. If atBegin is false or unspecified, callback will only be executed `delay` milliseconds
* after the last debounced-function call. If atBegin is true, callback will be executed only at the first debounced-function call.
* (After the throttled-function has not been called for `delay` milliseconds, the internal counter is reset).
*
* @returns {Function} A new, debounced function.
*/
function debounce (delay, callback, options) {
var _ref = options || {},
_ref$atBegin = _ref.atBegin,
atBegin = _ref$atBegin === void 0 ? false : _ref$atBegin;
return throttle(delay, callback, {
debounceMode: atBegin !== false
});
}
/*
How it works:
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
*/
class Node {
value;
next;
constructor(value) {
this.value = value;
}
}
class Queue {
#head;
#tail;
#size;
constructor() {
this.clear();
}
enqueue(value) {
const node = new Node(value);
if (this.#head) {
this.#tail.next = node;
this.#tail = node;
} else {
this.#head = node;
this.#tail = node;
}
this.#size++;
}
dequeue() {
const current = this.#head;
if (!current) {
return;
}
this.#head = this.#head.next;
this.#size--;
return current.value;
}
clear() {
this.#head = undefined;
this.#tail = undefined;
this.#size = 0;
}
get size() {
return this.#size;
}
* [Symbol.iterator]() {
let current = this.#head;
while (current) {
yield current.value;
current = current.next;
}
}
}
const AsyncResource = {
bind(fn, _type, thisArg) {
return fn.bind(thisArg);
},
};
function pLimit(concurrency) {
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
throw new TypeError('Expected `concurrency` to be a number from 1 and up');
}
const queue = new Queue();
let activeCount = 0;
const next = () => {
activeCount--;
if (queue.size > 0) {
queue.dequeue()();
}
};
const run = async (function_, resolve, arguments_) => {
activeCount++;
const result = (async () => function_(...arguments_))();
resolve(result);
try {
await result;
} catch {}
next();
};
const enqueue = (function_, resolve, arguments_) => {
queue.enqueue(
AsyncResource.bind(run.bind(undefined, function_, resolve, arguments_)),
);
(async () => {
// This function needs to wait until the next microtask before comparing
// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously
// when the run function is dequeued and called. The comparison in the if-statement
// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.
await Promise.resolve();
if (activeCount < concurrency && queue.size > 0) {
queue.dequeue()();
}
})();
};
const generator = (function_, ...arguments_) => new Promise(resolve => {
enqueue(function_, resolve, arguments_);
});
Object.defineProperties(generator, {
activeCount: {
get: () => activeCount,
},
pendingCount: {
get: () => queue.size,
},
clearQueue: {
value() {
queue.clear();
},
},
});
return generator;
}
const VOID = Symbol("p-void");
class PInstance extends Promise {
constructor(items = [], options) {
super(() => {
});
this.items = items;
this.options = options;
this.promises = /* @__PURE__ */ new Set();
}
get promise() {
var _a;
let batch;
const items = [...Array.from(this.items), ...Array.from(this.promises)];
if ((_a = this.options) == null ? void 0 : _a.concurrency) {
const limit = pLimit(this.options.concurrency);
batch = Promise.all(items.map((p2) => limit(() => p2)));
} else {
batch = Promise.all(items);
}
return batch.then((l) => l.filter((i) => i !== VOID));
}
add(...args) {
args.forEach((i) => {
this.promises.add(i);
});
}
map(fn) {
return new PInstance(
Array.from(this.items).map(async (i, idx) => {
const v = await i;
if (v === VOID)
return VOID;
return fn(v, idx);
}),
this.options
);
}
filter(fn) {
return new PInstance(
Array.from(this.items).map(async (i, idx) => {
const v = await i;
const r = await fn(v, idx);
if (!r)
return VOID;
return v;
}),
this.options
);
}
forEach(fn) {
return this.map(fn).then();
}
reduce(fn, initialValue) {
return this.promise.then((array) => array.reduce(fn, initialValue));
}
clear() {
this.promises.clear();
}
then(fn) {
const p2 = this.promise;
if (fn)
return p2.then(fn);
else
return p2;
}
catch(fn) {
return this.promise.catch(fn);
}
finally(fn) {
return this.promise.finally(fn);
}
}
function p(items, options) {
return new PInstance(items, options);
}
exports.assert = assert;
exports.at = at;
exports.batchInvoke = batchInvoke;
exports.capitalize = capitalize;
exports.clamp = clamp;
exports.clampArrayRange = clampArrayRange;
exports.clearUndefined = clearUndefined;
exports.createControlledPromise = createControlledPromise;
exports.createPromiseLock = createPromiseLock;
exports.createSingletonPromise = createSingletonPromise;
exports.debounce = debounce;
exports.deepMerge = deepMerge;
exports.deepMergeWithArray = deepMergeWithArray;
exports.ensurePrefix = ensurePrefix;
exports.ensureSuffix = ensureSuffix;
exports.flattenArrayable = flattenArrayable;
exports.getTypeName = getTypeName;
exports.hasOwnProperty = hasOwnProperty;
exports.invoke = invoke;
exports.isBoolean = isBoolean;
exports.isBrowser = isBrowser;
exports.isDate = isDate;
exports.isDeepEqual = isDeepEqual;
exports.isDef = isDef;
exports.isFunction = isFunction;
exports.isKeyOf = isKeyOf;
exports.isNull = isNull;
exports.isNumber = isNumber;
exports.isObject = isObject;
exports.isRegExp = isRegExp;
exports.isString = isString;
exports.isTruthy = isTruthy;
exports.isUndefined = isUndefined;
exports.isWindow = isWindow;
exports.last = last;
exports.lerp = lerp;
exports.mergeArrayable = mergeArrayable;
exports.move = move;
exports.noNull = noNull;
exports.noop = noop;
exports.notNullish = notNullish;
exports.notUndefined = notUndefined;
exports.objectEntries = objectEntries;
exports.objectKeys = objectKeys;
exports.objectMap = objectMap;
exports.objectPick = objectPick;
exports.p = p;
exports.partition = partition;
exports.randomStr = randomStr;
exports.range = range;
exports.remap = remap;
exports.remove = remove;
exports.sample = sample;
exports.shuffle = shuffle;
exports.slash = slash;
exports.sleep = sleep;
exports.sum = sum;
exports.tap = tap;
exports.template = template;
exports.throttle = throttle;
exports.timestamp = timestamp;
exports.toArray = toArray;
exports.toString = toString;
exports.unindent = unindent;
exports.uniq = uniq;
exports.uniqueBy = uniqueBy;
+614
View File
@@ -0,0 +1,614 @@
/**
* Promise, or maybe not
*/
type Awaitable<T> = T | PromiseLike<T>;
/**
* Null or whatever
*/
type Nullable<T> = T | null | undefined;
/**
* Array, or not yet
*/
type Arrayable<T> = T | Array<T>;
/**
* Function
*/
type Fn<T = void> = () => T;
/**
* Constructor
*/
type Constructor<T = void> = new (...args: any[]) => T;
/**
* Infers the element type of an array
*/
type ElementOf<T> = T extends (infer E)[] ? E : never;
/**
* Defines an intersection type of all union items.
*
* @param U Union of any types that will be intersected.
* @returns U items intersected
* @see https://stackoverflow.com/a/50375286/9259330
*/
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
/**
* Infers the arguments type of a function
*/
type ArgumentsType<T> = T extends ((...args: infer A) => any) ? A : never;
type MergeInsertions<T> = T extends object ? {
[K in keyof T]: MergeInsertions<T[K]>;
} : T;
type DeepMerge<F, S> = MergeInsertions<{
[K in keyof F | keyof S]: K extends keyof S & keyof F ? DeepMerge<F[K], S[K]> : K extends keyof S ? S[K] : K extends keyof F ? F[K] : never;
}>;
/**
* Convert `Arrayable<T>` to `Array<T>`
*
* @category Array
*/
declare function toArray<T>(array?: Nullable<Arrayable<T>>): Array<T>;
/**
* Convert `Arrayable<T>` to `Array<T>` and flatten it
*
* @category Array
*/
declare function flattenArrayable<T>(array?: Nullable<Arrayable<T | Array<T>>>): Array<T>;
/**
* Use rest arguments to merge arrays
*
* @category Array
*/
declare function mergeArrayable<T>(...args: Nullable<Arrayable<T>>[]): Array<T>;
type PartitionFilter<T> = (i: T, idx: number, arr: readonly T[]) => any;
/**
* Divide an array into two parts by a filter function
*
* @category Array
* @example const [odd, even] = partition([1, 2, 3, 4], i => i % 2 != 0)
*/
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>): [T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>): [T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>): [T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>): [T[], T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>, f5: PartitionFilter<T>): [T[], T[], T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>, f5: PartitionFilter<T>, f6: PartitionFilter<T>): [T[], T[], T[], T[], T[], T[], T[]];
/**
* Unique an Array
*
* @category Array
*/
declare function uniq<T>(array: readonly T[]): T[];
/**
* Unique an Array by a custom equality function
*
* @category Array
*/
declare function uniqueBy<T>(array: readonly T[], equalFn: (a: any, b: any) => boolean): T[];
/**
* Get last item
*
* @category Array
*/
declare function last(array: readonly []): undefined;
declare function last<T>(array: readonly T[]): T;
/**
* Remove an item from Array
*
* @category Array
*/
declare function remove<T>(array: T[], value: T): boolean;
/**
* Get nth item of Array. Negative for backward
*
* @category Array
*/
declare function at(array: readonly [], index: number): undefined;
declare function at<T>(array: readonly T[], index: number): T;
/**
* Genrate a range array of numbers. The `stop` is exclusive.
*
* @category Array
*/
declare function range(stop: number): number[];
declare function range(start: number, stop: number, step?: number): number[];
/**
* Move element in an Array
*
* @category Array
* @param arr
* @param from
* @param to
*/
declare function move<T>(arr: T[], from: number, to: number): T[];
/**
* Clamp a number to the index range of an array
*
* @category Array
*/
declare function clampArrayRange(n: number, arr: readonly unknown[]): number;
/**
* Get random item(s) from an array
*
* @param arr
* @param quantity - quantity of random items which will be returned
*/
declare function sample<T>(arr: T[], quantity: number): T[];
/**
* Shuffle an array. This function mutates the array.
*
* @category Array
*/
declare function shuffle<T>(array: T[]): T[];
declare function assert(condition: boolean, message: string): asserts condition;
declare const toString: (v: any) => string;
declare function getTypeName(v: any): string;
declare function noop(): void;
declare function isDeepEqual(value1: any, value2: any): boolean;
/**
* Type guard to filter out null-ish values
*
* @category Guards
* @example array.filter(notNullish)
*/
declare function notNullish<T>(v: T | null | undefined): v is NonNullable<T>;
/**
* Type guard to filter out null values
*
* @category Guards
* @example array.filter(noNull)
*/
declare function noNull<T>(v: T | null): v is Exclude<T, null>;
/**
* Type guard to filter out null-ish values
*
* @category Guards
* @example array.filter(notUndefined)
*/
declare function notUndefined<T>(v: T): v is Exclude<T, undefined>;
/**
* Type guard to filter out falsy values
*
* @category Guards
* @example array.filter(isTruthy)
*/
declare function isTruthy<T>(v: T): v is NonNullable<T>;
declare const isDef: <T = any>(val?: T) => val is T;
declare const isBoolean: (val: any) => val is boolean;
declare const isFunction: <T extends Function>(val: any) => val is T;
declare const isNumber: (val: any) => val is number;
declare const isString: (val: unknown) => val is string;
declare const isObject: (val: any) => val is object;
declare const isUndefined: (val: any) => val is undefined;
declare const isNull: (val: any) => val is null;
declare const isRegExp: (val: any) => val is RegExp;
declare const isDate: (val: any) => val is Date;
declare const isWindow: (val: any) => boolean;
declare const isBrowser: boolean;
declare function clamp(n: number, min: number, max: number): number;
declare function sum(...args: number[] | number[][]): number;
/**
* Linearly interpolates between `min` and `max` based on `t`
*
* @category Math
* @param min The minimum value
* @param max The maximum value
* @param t The interpolation value clamped between 0 and 1
* @example
* ```
* const value = lerp(0, 2, 0.5) // value will be 1
* ```
*/
declare function lerp(min: number, max: number, t: number): number;
/**
* Linearly remaps a clamped value from its source range [`inMin`, `inMax`] to the destination range [`outMin`, `outMax`]
*
* @category Math
* @example
* ```
* const value = remap(0.5, 0, 1, 200, 400) // value will be 300
* ```
*/
declare function remap(n: number, inMin: number, inMax: number, outMin: number, outMax: number): number;
/**
* Replace backslash to slash
*
* @category String
*/
declare function slash(str: string): string;
/**
* Ensure prefix of a string
*
* @category String
*/
declare function ensurePrefix(prefix: string, str: string): string;
/**
* Ensure suffix of a string
*
* @category String
*/
declare function ensureSuffix(suffix: string, str: string): string;
/**
* Dead simple template engine, just like Python's `.format()`
* Support passing variables as either in index based or object/name based approach
* While using object/name based approach, you can pass a fallback value as the third argument
*
* @category String
* @example
* ```
* const result = template(
* 'Hello {0}! My name is {1}.',
* 'Inès',
* 'Anthony'
* ) // Hello Inès! My name is Anthony.
* ```
*
* ```
* const result = namedTemplate(
* '{greet}! My name is {name}.',
* { greet: 'Hello', name: 'Anthony' }
* ) // Hello! My name is Anthony.
* ```
*
* const result = namedTemplate(
* '{greet}! My name is {name}.',
* { greet: 'Hello' }, // name isn't passed hence fallback will be used for name
* 'placeholder'
* ) // Hello! My name is placeholder.
* ```
*/
declare function template(str: string, object: Record<string | number, any>, fallback?: string | ((key: string) => string)): string;
declare function template(str: string, ...args: (string | number | bigint | undefined | null)[]): string;
/**
* Generate a random string
* @category String
*/
declare function randomStr(size?: number, dict?: string): string;
/**
* First letter uppercase, other lowercase
* @category string
* @example
* ```
* capitalize('hello') => 'Hello'
* ```
*/
declare function capitalize(str: string): string;
/**
* Remove common leading whitespace from a template string.
* Will also remove empty lines at the beginning and end.
* @category string
* @example
* ```ts
* const str = unindent`
* if (a) {
* b()
* }
* `
*/
declare function unindent(str: TemplateStringsArray | string): string;
declare const timestamp: () => number;
/**
* Call every function in an array
*/
declare function batchInvoke(functions: Nullable<Fn>[]): void;
/**
* Call the function
*/
declare function invoke(fn: Fn): void;
/**
* Pass the value through the callback, and return the value
*
* @example
* ```
* function createUser(name: string): User {
* return tap(new User, user => {
* user.name = name
* })
* }
* ```
*/
declare function tap<T>(value: T, callback: (value: T) => void): T;
/**
* Map key/value pairs for an object, and construct a new one
*
*
* @category Object
*
* Transform:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => [k.toString().toUpperCase(), v.toString()])
* // { A: '1', B: '2' }
* ```
*
* Swap key/value:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => [v, k])
* // { 1: 'a', 2: 'b' }
* ```
*
* Filter keys:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => k === 'a' ? undefined : [k, v])
* // { b: 2 }
* ```
*/
declare function objectMap<K extends string, V, NK extends string | number | symbol = K, NV = V>(obj: Record<K, V>, fn: (key: K, value: V) => [NK, NV] | undefined): Record<NK, NV>;
/**
* Type guard for any key, `k`.
* Marks `k` as a key of `T` if `k` is in `obj`.
*
* @category Object
* @param obj object to query for key `k`
* @param k key to check existence in `obj`
*/
declare function isKeyOf<T extends object>(obj: T, k: keyof any): k is keyof T;
/**
* Strict typed `Object.keys`
*
* @category Object
*/
declare function objectKeys<T extends object>(obj: T): (`${keyof T & undefined}` | `${keyof T & null}` | `${keyof T & string}` | `${keyof T & number}` | `${keyof T & false}` | `${keyof T & true}`)[];
/**
* Strict typed `Object.entries`
*
* @category Object
*/
declare function objectEntries<T extends object>(obj: T): [keyof T, T[keyof T]][];
/**
* Deep merge
*
* The first argument is the target object, the rest are the sources.
* The target object will be mutated and returned.
*
* @category Object
*/
declare function deepMerge<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S>;
/**
* Deep merge
*
* Differs from `deepMerge` in that it merges arrays instead of overriding them.
*
* The first argument is the target object, the rest are the sources.
* The target object will be mutated and returned.
*
* @category Object
*/
declare function deepMergeWithArray<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S>;
/**
* Create a new subset object by giving keys
*
* @category Object
*/
declare function objectPick<O extends object, T extends keyof O>(obj: O, keys: T[], omitUndefined?: boolean): Pick<O, T>;
/**
* Clear undefined fields from an object. It mutates the object
*
* @category Object
*/
declare function clearUndefined<T extends object>(obj: T): T;
/**
* Determines whether an object has a property with the specified name
*
* @see https://eslint.org/docs/rules/no-prototype-builtins
* @category Object
*/
declare function hasOwnProperty<T>(obj: T, v: PropertyKey): boolean;
interface SingletonPromiseReturn<T> {
(): Promise<T>;
/**
* Reset current staled promise.
* Await it to have proper shutdown.
*/
reset: () => Promise<void>;
}
/**
* Create singleton promise function
*
* @category Promise
*/
declare function createSingletonPromise<T>(fn: () => Promise<T>): SingletonPromiseReturn<T>;
/**
* Promised `setTimeout`
*
* @category Promise
*/
declare function sleep(ms: number, callback?: Fn<any>): Promise<void>;
/**
* Create a promise lock
*
* @category Promise
* @example
* ```
* const lock = createPromiseLock()
*
* lock.run(async () => {
* await doSomething()
* })
*
* // in anther context:
* await lock.wait() // it will wait all tasking finished
* ```
*/
declare function createPromiseLock(): {
run<T = void>(fn: () => Promise<T>): Promise<T>;
wait(): Promise<void>;
isWaiting(): boolean;
clear(): void;
};
/**
* Promise with `resolve` and `reject` methods of itself
*/
interface ControlledPromise<T = void> extends Promise<T> {
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
}
/**
* Return a Promise with `resolve` and `reject` methods
*
* @category Promise
* @example
* ```
* const promise = createControlledPromise()
*
* await promise
*
* // in anther context:
* promise.resolve(data)
* ```
*/
declare function createControlledPromise<T>(): ControlledPromise<T>;
interface CancelOptions {
upcomingOnly?: boolean;
}
interface Cancel {
cancel: (options?: CancelOptions) => void;
}
interface NoReturn<T extends (...args: any[]) => any> {
(...args: Parameters<T>): void;
}
interface ThrottleOptions {
noTrailing?: boolean;
noLeading?: boolean;
debounceMode?: boolean;
}
interface DebounceOptions {
atBegin?: boolean;
}
type throttle<T extends (...args: any[]) => any> = NoReturn<T> & Cancel;
/**
* Throttle execution of a function. Especially useful for rate limiting
* execution of handlers on events like resize and scroll.
*
* @param delay
* A zero-or-greater delay in milliseconds. For event callbacks, values around
* 100 or 250 (or even higher) are most useful.
*
* @param callback
* A function to be executed after delay milliseconds. The `this` context and
* all arguments are passed through, as-is, to `callback` when the
* throttled-function is executed.
*
* @param options
* An object to configure options.
*
* @param options.noTrailing
* Optional, defaults to false. If noTrailing is true, callback will only execute
* every `delay` milliseconds while the throttled-function is being called. If
* noTrailing is false or unspecified, callback will be executed one final time
* after the last throttled-function call. (After the throttled-function has not
* been called for `delay` milliseconds, the internal counter is reset)
*
* @param options.noLeading
* Optional, defaults to false. If noLeading is false, the first throttled-function
* call will execute callback immediately. If noLeading is true, the first the
* callback execution will be skipped. It should be noted that callback will never
* executed if both noLeading = true and noTrailing = true.
*
* @param options.debounceMode If `debounceMode` is true (at begin), schedule
* `callback` to execute after `delay` ms. If `debounceMode` is false (at end),
* schedule `callback` to execute after `delay` ms.
*
* @return
* A new, throttled, function.
*/
declare function throttle<T extends (...args: any[]) => any>(
delay: number,
callback: T,
options?: ThrottleOptions,
): throttle<T>;
type debounce<T extends (...args: any[]) => any> = NoReturn<T> & Cancel;
/**
* Debounce execution of a function. Debouncing, unlike throttling,
* guarantees that a function is only executed a single time, either at the
* very beginning of a series of calls, or at the very end.
*
* @param delay
* A zero-or-greater delay in milliseconds. For event callbacks, values around
* 100 or 250 (or even higher) are most useful.
*
* @param callback
* A function to be executed after delay milliseconds. The `this` context and
* all arguments are passed through, as-is, to `callback` when the
* debounced-function is executed.
*
* @param options
* An object to configure options.
*
* @param options.atBegin
* If atBegin is false or unspecified, callback will only be executed `delay`
* milliseconds after the last debounced-function call. If atBegin is true,
* callback will be executed only at the first debounced-function call. (After
* the throttled-function has not been called for `delay` milliseconds, the
* internal counter is reset).
*
* @return
* A new, debounced function.
*/
declare function debounce<T extends (...args: any[]) => any>(
delay: number,
callback: T,
options?: DebounceOptions,
): debounce<T>;
interface POptions {
/**
* How many promises are resolved at the same time.
*/
concurrency?: number | undefined;
}
declare class PInstance<T = any> extends Promise<Awaited<T>[]> {
items: Iterable<T>;
options?: POptions | undefined;
private promises;
get promise(): Promise<Awaited<T>[]>;
constructor(items?: Iterable<T>, options?: POptions | undefined);
add(...args: (T | Promise<T>)[]): void;
map<U>(fn: (value: Awaited<T>, index: number) => U): PInstance<Promise<U>>;
filter(fn: (value: Awaited<T>, index: number) => boolean | Promise<boolean>): PInstance<Promise<T>>;
forEach(fn: (value: Awaited<T>, index: number) => void): Promise<void>;
reduce<U>(fn: (previousValue: U, currentValue: Awaited<T>, currentIndex: number, array: Awaited<T>[]) => U, initialValue: U): Promise<U>;
clear(): void;
then(fn?: () => PromiseLike<any>): Promise<any>;
catch(fn?: (err: unknown) => PromiseLike<any>): Promise<any>;
finally(fn?: () => void): Promise<Awaited<T>[]>;
}
/**
* Utility for managing multiple promises.
*
* @see https://github.com/antfu/utils/tree/main/docs/p.md
* @category Promise
* @example
* ```
* import { p } from '@antfu/utils'
*
* const items = [1, 2, 3, 4, 5]
*
* await p(items)
* .map(async i => await multiply(i, 3))
* .filter(async i => await isEven(i))
* // [6, 12]
* ```
*/
declare function p<T = any>(items?: Iterable<T>, options?: POptions): PInstance<T>;
export { type ArgumentsType, type Arrayable, type Awaitable, type Constructor, type ControlledPromise, type DeepMerge, type ElementOf, type Fn, type MergeInsertions, type Nullable, type PartitionFilter, type SingletonPromiseReturn, type UnionToIntersection, assert, at, batchInvoke, capitalize, clamp, clampArrayRange, clearUndefined, createControlledPromise, createPromiseLock, createSingletonPromise, debounce, deepMerge, deepMergeWithArray, ensurePrefix, ensureSuffix, flattenArrayable, getTypeName, hasOwnProperty, invoke, isBoolean, isBrowser, isDate, isDeepEqual, isDef, isFunction, isKeyOf, isNull, isNumber, isObject, isRegExp, isString, isTruthy, isUndefined, isWindow, last, lerp, mergeArrayable, move, noNull, noop, notNullish, notUndefined, objectEntries, objectKeys, objectMap, objectPick, p, partition, randomStr, range, remap, remove, sample, shuffle, slash, sleep, sum, tap, template, throttle, timestamp, toArray, toString, unindent, uniq, uniqueBy };
+614
View File
@@ -0,0 +1,614 @@
/**
* Promise, or maybe not
*/
type Awaitable<T> = T | PromiseLike<T>;
/**
* Null or whatever
*/
type Nullable<T> = T | null | undefined;
/**
* Array, or not yet
*/
type Arrayable<T> = T | Array<T>;
/**
* Function
*/
type Fn<T = void> = () => T;
/**
* Constructor
*/
type Constructor<T = void> = new (...args: any[]) => T;
/**
* Infers the element type of an array
*/
type ElementOf<T> = T extends (infer E)[] ? E : never;
/**
* Defines an intersection type of all union items.
*
* @param U Union of any types that will be intersected.
* @returns U items intersected
* @see https://stackoverflow.com/a/50375286/9259330
*/
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
/**
* Infers the arguments type of a function
*/
type ArgumentsType<T> = T extends ((...args: infer A) => any) ? A : never;
type MergeInsertions<T> = T extends object ? {
[K in keyof T]: MergeInsertions<T[K]>;
} : T;
type DeepMerge<F, S> = MergeInsertions<{
[K in keyof F | keyof S]: K extends keyof S & keyof F ? DeepMerge<F[K], S[K]> : K extends keyof S ? S[K] : K extends keyof F ? F[K] : never;
}>;
/**
* Convert `Arrayable<T>` to `Array<T>`
*
* @category Array
*/
declare function toArray<T>(array?: Nullable<Arrayable<T>>): Array<T>;
/**
* Convert `Arrayable<T>` to `Array<T>` and flatten it
*
* @category Array
*/
declare function flattenArrayable<T>(array?: Nullable<Arrayable<T | Array<T>>>): Array<T>;
/**
* Use rest arguments to merge arrays
*
* @category Array
*/
declare function mergeArrayable<T>(...args: Nullable<Arrayable<T>>[]): Array<T>;
type PartitionFilter<T> = (i: T, idx: number, arr: readonly T[]) => any;
/**
* Divide an array into two parts by a filter function
*
* @category Array
* @example const [odd, even] = partition([1, 2, 3, 4], i => i % 2 != 0)
*/
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>): [T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>): [T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>): [T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>): [T[], T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>, f5: PartitionFilter<T>): [T[], T[], T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>, f5: PartitionFilter<T>, f6: PartitionFilter<T>): [T[], T[], T[], T[], T[], T[], T[]];
/**
* Unique an Array
*
* @category Array
*/
declare function uniq<T>(array: readonly T[]): T[];
/**
* Unique an Array by a custom equality function
*
* @category Array
*/
declare function uniqueBy<T>(array: readonly T[], equalFn: (a: any, b: any) => boolean): T[];
/**
* Get last item
*
* @category Array
*/
declare function last(array: readonly []): undefined;
declare function last<T>(array: readonly T[]): T;
/**
* Remove an item from Array
*
* @category Array
*/
declare function remove<T>(array: T[], value: T): boolean;
/**
* Get nth item of Array. Negative for backward
*
* @category Array
*/
declare function at(array: readonly [], index: number): undefined;
declare function at<T>(array: readonly T[], index: number): T;
/**
* Genrate a range array of numbers. The `stop` is exclusive.
*
* @category Array
*/
declare function range(stop: number): number[];
declare function range(start: number, stop: number, step?: number): number[];
/**
* Move element in an Array
*
* @category Array
* @param arr
* @param from
* @param to
*/
declare function move<T>(arr: T[], from: number, to: number): T[];
/**
* Clamp a number to the index range of an array
*
* @category Array
*/
declare function clampArrayRange(n: number, arr: readonly unknown[]): number;
/**
* Get random item(s) from an array
*
* @param arr
* @param quantity - quantity of random items which will be returned
*/
declare function sample<T>(arr: T[], quantity: number): T[];
/**
* Shuffle an array. This function mutates the array.
*
* @category Array
*/
declare function shuffle<T>(array: T[]): T[];
declare function assert(condition: boolean, message: string): asserts condition;
declare const toString: (v: any) => string;
declare function getTypeName(v: any): string;
declare function noop(): void;
declare function isDeepEqual(value1: any, value2: any): boolean;
/**
* Type guard to filter out null-ish values
*
* @category Guards
* @example array.filter(notNullish)
*/
declare function notNullish<T>(v: T | null | undefined): v is NonNullable<T>;
/**
* Type guard to filter out null values
*
* @category Guards
* @example array.filter(noNull)
*/
declare function noNull<T>(v: T | null): v is Exclude<T, null>;
/**
* Type guard to filter out null-ish values
*
* @category Guards
* @example array.filter(notUndefined)
*/
declare function notUndefined<T>(v: T): v is Exclude<T, undefined>;
/**
* Type guard to filter out falsy values
*
* @category Guards
* @example array.filter(isTruthy)
*/
declare function isTruthy<T>(v: T): v is NonNullable<T>;
declare const isDef: <T = any>(val?: T) => val is T;
declare const isBoolean: (val: any) => val is boolean;
declare const isFunction: <T extends Function>(val: any) => val is T;
declare const isNumber: (val: any) => val is number;
declare const isString: (val: unknown) => val is string;
declare const isObject: (val: any) => val is object;
declare const isUndefined: (val: any) => val is undefined;
declare const isNull: (val: any) => val is null;
declare const isRegExp: (val: any) => val is RegExp;
declare const isDate: (val: any) => val is Date;
declare const isWindow: (val: any) => boolean;
declare const isBrowser: boolean;
declare function clamp(n: number, min: number, max: number): number;
declare function sum(...args: number[] | number[][]): number;
/**
* Linearly interpolates between `min` and `max` based on `t`
*
* @category Math
* @param min The minimum value
* @param max The maximum value
* @param t The interpolation value clamped between 0 and 1
* @example
* ```
* const value = lerp(0, 2, 0.5) // value will be 1
* ```
*/
declare function lerp(min: number, max: number, t: number): number;
/**
* Linearly remaps a clamped value from its source range [`inMin`, `inMax`] to the destination range [`outMin`, `outMax`]
*
* @category Math
* @example
* ```
* const value = remap(0.5, 0, 1, 200, 400) // value will be 300
* ```
*/
declare function remap(n: number, inMin: number, inMax: number, outMin: number, outMax: number): number;
/**
* Replace backslash to slash
*
* @category String
*/
declare function slash(str: string): string;
/**
* Ensure prefix of a string
*
* @category String
*/
declare function ensurePrefix(prefix: string, str: string): string;
/**
* Ensure suffix of a string
*
* @category String
*/
declare function ensureSuffix(suffix: string, str: string): string;
/**
* Dead simple template engine, just like Python's `.format()`
* Support passing variables as either in index based or object/name based approach
* While using object/name based approach, you can pass a fallback value as the third argument
*
* @category String
* @example
* ```
* const result = template(
* 'Hello {0}! My name is {1}.',
* 'Inès',
* 'Anthony'
* ) // Hello Inès! My name is Anthony.
* ```
*
* ```
* const result = namedTemplate(
* '{greet}! My name is {name}.',
* { greet: 'Hello', name: 'Anthony' }
* ) // Hello! My name is Anthony.
* ```
*
* const result = namedTemplate(
* '{greet}! My name is {name}.',
* { greet: 'Hello' }, // name isn't passed hence fallback will be used for name
* 'placeholder'
* ) // Hello! My name is placeholder.
* ```
*/
declare function template(str: string, object: Record<string | number, any>, fallback?: string | ((key: string) => string)): string;
declare function template(str: string, ...args: (string | number | bigint | undefined | null)[]): string;
/**
* Generate a random string
* @category String
*/
declare function randomStr(size?: number, dict?: string): string;
/**
* First letter uppercase, other lowercase
* @category string
* @example
* ```
* capitalize('hello') => 'Hello'
* ```
*/
declare function capitalize(str: string): string;
/**
* Remove common leading whitespace from a template string.
* Will also remove empty lines at the beginning and end.
* @category string
* @example
* ```ts
* const str = unindent`
* if (a) {
* b()
* }
* `
*/
declare function unindent(str: TemplateStringsArray | string): string;
declare const timestamp: () => number;
/**
* Call every function in an array
*/
declare function batchInvoke(functions: Nullable<Fn>[]): void;
/**
* Call the function
*/
declare function invoke(fn: Fn): void;
/**
* Pass the value through the callback, and return the value
*
* @example
* ```
* function createUser(name: string): User {
* return tap(new User, user => {
* user.name = name
* })
* }
* ```
*/
declare function tap<T>(value: T, callback: (value: T) => void): T;
/**
* Map key/value pairs for an object, and construct a new one
*
*
* @category Object
*
* Transform:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => [k.toString().toUpperCase(), v.toString()])
* // { A: '1', B: '2' }
* ```
*
* Swap key/value:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => [v, k])
* // { 1: 'a', 2: 'b' }
* ```
*
* Filter keys:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => k === 'a' ? undefined : [k, v])
* // { b: 2 }
* ```
*/
declare function objectMap<K extends string, V, NK extends string | number | symbol = K, NV = V>(obj: Record<K, V>, fn: (key: K, value: V) => [NK, NV] | undefined): Record<NK, NV>;
/**
* Type guard for any key, `k`.
* Marks `k` as a key of `T` if `k` is in `obj`.
*
* @category Object
* @param obj object to query for key `k`
* @param k key to check existence in `obj`
*/
declare function isKeyOf<T extends object>(obj: T, k: keyof any): k is keyof T;
/**
* Strict typed `Object.keys`
*
* @category Object
*/
declare function objectKeys<T extends object>(obj: T): (`${keyof T & undefined}` | `${keyof T & null}` | `${keyof T & string}` | `${keyof T & number}` | `${keyof T & false}` | `${keyof T & true}`)[];
/**
* Strict typed `Object.entries`
*
* @category Object
*/
declare function objectEntries<T extends object>(obj: T): [keyof T, T[keyof T]][];
/**
* Deep merge
*
* The first argument is the target object, the rest are the sources.
* The target object will be mutated and returned.
*
* @category Object
*/
declare function deepMerge<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S>;
/**
* Deep merge
*
* Differs from `deepMerge` in that it merges arrays instead of overriding them.
*
* The first argument is the target object, the rest are the sources.
* The target object will be mutated and returned.
*
* @category Object
*/
declare function deepMergeWithArray<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S>;
/**
* Create a new subset object by giving keys
*
* @category Object
*/
declare function objectPick<O extends object, T extends keyof O>(obj: O, keys: T[], omitUndefined?: boolean): Pick<O, T>;
/**
* Clear undefined fields from an object. It mutates the object
*
* @category Object
*/
declare function clearUndefined<T extends object>(obj: T): T;
/**
* Determines whether an object has a property with the specified name
*
* @see https://eslint.org/docs/rules/no-prototype-builtins
* @category Object
*/
declare function hasOwnProperty<T>(obj: T, v: PropertyKey): boolean;
interface SingletonPromiseReturn<T> {
(): Promise<T>;
/**
* Reset current staled promise.
* Await it to have proper shutdown.
*/
reset: () => Promise<void>;
}
/**
* Create singleton promise function
*
* @category Promise
*/
declare function createSingletonPromise<T>(fn: () => Promise<T>): SingletonPromiseReturn<T>;
/**
* Promised `setTimeout`
*
* @category Promise
*/
declare function sleep(ms: number, callback?: Fn<any>): Promise<void>;
/**
* Create a promise lock
*
* @category Promise
* @example
* ```
* const lock = createPromiseLock()
*
* lock.run(async () => {
* await doSomething()
* })
*
* // in anther context:
* await lock.wait() // it will wait all tasking finished
* ```
*/
declare function createPromiseLock(): {
run<T = void>(fn: () => Promise<T>): Promise<T>;
wait(): Promise<void>;
isWaiting(): boolean;
clear(): void;
};
/**
* Promise with `resolve` and `reject` methods of itself
*/
interface ControlledPromise<T = void> extends Promise<T> {
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
}
/**
* Return a Promise with `resolve` and `reject` methods
*
* @category Promise
* @example
* ```
* const promise = createControlledPromise()
*
* await promise
*
* // in anther context:
* promise.resolve(data)
* ```
*/
declare function createControlledPromise<T>(): ControlledPromise<T>;
interface CancelOptions {
upcomingOnly?: boolean;
}
interface Cancel {
cancel: (options?: CancelOptions) => void;
}
interface NoReturn<T extends (...args: any[]) => any> {
(...args: Parameters<T>): void;
}
interface ThrottleOptions {
noTrailing?: boolean;
noLeading?: boolean;
debounceMode?: boolean;
}
interface DebounceOptions {
atBegin?: boolean;
}
type throttle<T extends (...args: any[]) => any> = NoReturn<T> & Cancel;
/**
* Throttle execution of a function. Especially useful for rate limiting
* execution of handlers on events like resize and scroll.
*
* @param delay
* A zero-or-greater delay in milliseconds. For event callbacks, values around
* 100 or 250 (or even higher) are most useful.
*
* @param callback
* A function to be executed after delay milliseconds. The `this` context and
* all arguments are passed through, as-is, to `callback` when the
* throttled-function is executed.
*
* @param options
* An object to configure options.
*
* @param options.noTrailing
* Optional, defaults to false. If noTrailing is true, callback will only execute
* every `delay` milliseconds while the throttled-function is being called. If
* noTrailing is false or unspecified, callback will be executed one final time
* after the last throttled-function call. (After the throttled-function has not
* been called for `delay` milliseconds, the internal counter is reset)
*
* @param options.noLeading
* Optional, defaults to false. If noLeading is false, the first throttled-function
* call will execute callback immediately. If noLeading is true, the first the
* callback execution will be skipped. It should be noted that callback will never
* executed if both noLeading = true and noTrailing = true.
*
* @param options.debounceMode If `debounceMode` is true (at begin), schedule
* `callback` to execute after `delay` ms. If `debounceMode` is false (at end),
* schedule `callback` to execute after `delay` ms.
*
* @return
* A new, throttled, function.
*/
declare function throttle<T extends (...args: any[]) => any>(
delay: number,
callback: T,
options?: ThrottleOptions,
): throttle<T>;
type debounce<T extends (...args: any[]) => any> = NoReturn<T> & Cancel;
/**
* Debounce execution of a function. Debouncing, unlike throttling,
* guarantees that a function is only executed a single time, either at the
* very beginning of a series of calls, or at the very end.
*
* @param delay
* A zero-or-greater delay in milliseconds. For event callbacks, values around
* 100 or 250 (or even higher) are most useful.
*
* @param callback
* A function to be executed after delay milliseconds. The `this` context and
* all arguments are passed through, as-is, to `callback` when the
* debounced-function is executed.
*
* @param options
* An object to configure options.
*
* @param options.atBegin
* If atBegin is false or unspecified, callback will only be executed `delay`
* milliseconds after the last debounced-function call. If atBegin is true,
* callback will be executed only at the first debounced-function call. (After
* the throttled-function has not been called for `delay` milliseconds, the
* internal counter is reset).
*
* @return
* A new, debounced function.
*/
declare function debounce<T extends (...args: any[]) => any>(
delay: number,
callback: T,
options?: DebounceOptions,
): debounce<T>;
interface POptions {
/**
* How many promises are resolved at the same time.
*/
concurrency?: number | undefined;
}
declare class PInstance<T = any> extends Promise<Awaited<T>[]> {
items: Iterable<T>;
options?: POptions | undefined;
private promises;
get promise(): Promise<Awaited<T>[]>;
constructor(items?: Iterable<T>, options?: POptions | undefined);
add(...args: (T | Promise<T>)[]): void;
map<U>(fn: (value: Awaited<T>, index: number) => U): PInstance<Promise<U>>;
filter(fn: (value: Awaited<T>, index: number) => boolean | Promise<boolean>): PInstance<Promise<T>>;
forEach(fn: (value: Awaited<T>, index: number) => void): Promise<void>;
reduce<U>(fn: (previousValue: U, currentValue: Awaited<T>, currentIndex: number, array: Awaited<T>[]) => U, initialValue: U): Promise<U>;
clear(): void;
then(fn?: () => PromiseLike<any>): Promise<any>;
catch(fn?: (err: unknown) => PromiseLike<any>): Promise<any>;
finally(fn?: () => void): Promise<Awaited<T>[]>;
}
/**
* Utility for managing multiple promises.
*
* @see https://github.com/antfu/utils/tree/main/docs/p.md
* @category Promise
* @example
* ```
* import { p } from '@antfu/utils'
*
* const items = [1, 2, 3, 4, 5]
*
* await p(items)
* .map(async i => await multiply(i, 3))
* .filter(async i => await isEven(i))
* // [6, 12]
* ```
*/
declare function p<T = any>(items?: Iterable<T>, options?: POptions): PInstance<T>;
export { type ArgumentsType, type Arrayable, type Awaitable, type Constructor, type ControlledPromise, type DeepMerge, type ElementOf, type Fn, type MergeInsertions, type Nullable, type PartitionFilter, type SingletonPromiseReturn, type UnionToIntersection, assert, at, batchInvoke, capitalize, clamp, clampArrayRange, clearUndefined, createControlledPromise, createPromiseLock, createSingletonPromise, debounce, deepMerge, deepMergeWithArray, ensurePrefix, ensureSuffix, flattenArrayable, getTypeName, hasOwnProperty, invoke, isBoolean, isBrowser, isDate, isDeepEqual, isDef, isFunction, isKeyOf, isNull, isNumber, isObject, isRegExp, isString, isTruthy, isUndefined, isWindow, last, lerp, mergeArrayable, move, noNull, noop, notNullish, notUndefined, objectEntries, objectKeys, objectMap, objectPick, p, partition, randomStr, range, remap, remove, sample, shuffle, slash, sleep, sum, tap, template, throttle, timestamp, toArray, toString, unindent, uniq, uniqueBy };
+614
View File
@@ -0,0 +1,614 @@
/**
* Promise, or maybe not
*/
type Awaitable<T> = T | PromiseLike<T>;
/**
* Null or whatever
*/
type Nullable<T> = T | null | undefined;
/**
* Array, or not yet
*/
type Arrayable<T> = T | Array<T>;
/**
* Function
*/
type Fn<T = void> = () => T;
/**
* Constructor
*/
type Constructor<T = void> = new (...args: any[]) => T;
/**
* Infers the element type of an array
*/
type ElementOf<T> = T extends (infer E)[] ? E : never;
/**
* Defines an intersection type of all union items.
*
* @param U Union of any types that will be intersected.
* @returns U items intersected
* @see https://stackoverflow.com/a/50375286/9259330
*/
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
/**
* Infers the arguments type of a function
*/
type ArgumentsType<T> = T extends ((...args: infer A) => any) ? A : never;
type MergeInsertions<T> = T extends object ? {
[K in keyof T]: MergeInsertions<T[K]>;
} : T;
type DeepMerge<F, S> = MergeInsertions<{
[K in keyof F | keyof S]: K extends keyof S & keyof F ? DeepMerge<F[K], S[K]> : K extends keyof S ? S[K] : K extends keyof F ? F[K] : never;
}>;
/**
* Convert `Arrayable<T>` to `Array<T>`
*
* @category Array
*/
declare function toArray<T>(array?: Nullable<Arrayable<T>>): Array<T>;
/**
* Convert `Arrayable<T>` to `Array<T>` and flatten it
*
* @category Array
*/
declare function flattenArrayable<T>(array?: Nullable<Arrayable<T | Array<T>>>): Array<T>;
/**
* Use rest arguments to merge arrays
*
* @category Array
*/
declare function mergeArrayable<T>(...args: Nullable<Arrayable<T>>[]): Array<T>;
type PartitionFilter<T> = (i: T, idx: number, arr: readonly T[]) => any;
/**
* Divide an array into two parts by a filter function
*
* @category Array
* @example const [odd, even] = partition([1, 2, 3, 4], i => i % 2 != 0)
*/
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>): [T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>): [T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>): [T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>): [T[], T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>, f5: PartitionFilter<T>): [T[], T[], T[], T[], T[], T[]];
declare function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>, f5: PartitionFilter<T>, f6: PartitionFilter<T>): [T[], T[], T[], T[], T[], T[], T[]];
/**
* Unique an Array
*
* @category Array
*/
declare function uniq<T>(array: readonly T[]): T[];
/**
* Unique an Array by a custom equality function
*
* @category Array
*/
declare function uniqueBy<T>(array: readonly T[], equalFn: (a: any, b: any) => boolean): T[];
/**
* Get last item
*
* @category Array
*/
declare function last(array: readonly []): undefined;
declare function last<T>(array: readonly T[]): T;
/**
* Remove an item from Array
*
* @category Array
*/
declare function remove<T>(array: T[], value: T): boolean;
/**
* Get nth item of Array. Negative for backward
*
* @category Array
*/
declare function at(array: readonly [], index: number): undefined;
declare function at<T>(array: readonly T[], index: number): T;
/**
* Genrate a range array of numbers. The `stop` is exclusive.
*
* @category Array
*/
declare function range(stop: number): number[];
declare function range(start: number, stop: number, step?: number): number[];
/**
* Move element in an Array
*
* @category Array
* @param arr
* @param from
* @param to
*/
declare function move<T>(arr: T[], from: number, to: number): T[];
/**
* Clamp a number to the index range of an array
*
* @category Array
*/
declare function clampArrayRange(n: number, arr: readonly unknown[]): number;
/**
* Get random item(s) from an array
*
* @param arr
* @param quantity - quantity of random items which will be returned
*/
declare function sample<T>(arr: T[], quantity: number): T[];
/**
* Shuffle an array. This function mutates the array.
*
* @category Array
*/
declare function shuffle<T>(array: T[]): T[];
declare function assert(condition: boolean, message: string): asserts condition;
declare const toString: (v: any) => string;
declare function getTypeName(v: any): string;
declare function noop(): void;
declare function isDeepEqual(value1: any, value2: any): boolean;
/**
* Type guard to filter out null-ish values
*
* @category Guards
* @example array.filter(notNullish)
*/
declare function notNullish<T>(v: T | null | undefined): v is NonNullable<T>;
/**
* Type guard to filter out null values
*
* @category Guards
* @example array.filter(noNull)
*/
declare function noNull<T>(v: T | null): v is Exclude<T, null>;
/**
* Type guard to filter out null-ish values
*
* @category Guards
* @example array.filter(notUndefined)
*/
declare function notUndefined<T>(v: T): v is Exclude<T, undefined>;
/**
* Type guard to filter out falsy values
*
* @category Guards
* @example array.filter(isTruthy)
*/
declare function isTruthy<T>(v: T): v is NonNullable<T>;
declare const isDef: <T = any>(val?: T) => val is T;
declare const isBoolean: (val: any) => val is boolean;
declare const isFunction: <T extends Function>(val: any) => val is T;
declare const isNumber: (val: any) => val is number;
declare const isString: (val: unknown) => val is string;
declare const isObject: (val: any) => val is object;
declare const isUndefined: (val: any) => val is undefined;
declare const isNull: (val: any) => val is null;
declare const isRegExp: (val: any) => val is RegExp;
declare const isDate: (val: any) => val is Date;
declare const isWindow: (val: any) => boolean;
declare const isBrowser: boolean;
declare function clamp(n: number, min: number, max: number): number;
declare function sum(...args: number[] | number[][]): number;
/**
* Linearly interpolates between `min` and `max` based on `t`
*
* @category Math
* @param min The minimum value
* @param max The maximum value
* @param t The interpolation value clamped between 0 and 1
* @example
* ```
* const value = lerp(0, 2, 0.5) // value will be 1
* ```
*/
declare function lerp(min: number, max: number, t: number): number;
/**
* Linearly remaps a clamped value from its source range [`inMin`, `inMax`] to the destination range [`outMin`, `outMax`]
*
* @category Math
* @example
* ```
* const value = remap(0.5, 0, 1, 200, 400) // value will be 300
* ```
*/
declare function remap(n: number, inMin: number, inMax: number, outMin: number, outMax: number): number;
/**
* Replace backslash to slash
*
* @category String
*/
declare function slash(str: string): string;
/**
* Ensure prefix of a string
*
* @category String
*/
declare function ensurePrefix(prefix: string, str: string): string;
/**
* Ensure suffix of a string
*
* @category String
*/
declare function ensureSuffix(suffix: string, str: string): string;
/**
* Dead simple template engine, just like Python's `.format()`
* Support passing variables as either in index based or object/name based approach
* While using object/name based approach, you can pass a fallback value as the third argument
*
* @category String
* @example
* ```
* const result = template(
* 'Hello {0}! My name is {1}.',
* 'Inès',
* 'Anthony'
* ) // Hello Inès! My name is Anthony.
* ```
*
* ```
* const result = namedTemplate(
* '{greet}! My name is {name}.',
* { greet: 'Hello', name: 'Anthony' }
* ) // Hello! My name is Anthony.
* ```
*
* const result = namedTemplate(
* '{greet}! My name is {name}.',
* { greet: 'Hello' }, // name isn't passed hence fallback will be used for name
* 'placeholder'
* ) // Hello! My name is placeholder.
* ```
*/
declare function template(str: string, object: Record<string | number, any>, fallback?: string | ((key: string) => string)): string;
declare function template(str: string, ...args: (string | number | bigint | undefined | null)[]): string;
/**
* Generate a random string
* @category String
*/
declare function randomStr(size?: number, dict?: string): string;
/**
* First letter uppercase, other lowercase
* @category string
* @example
* ```
* capitalize('hello') => 'Hello'
* ```
*/
declare function capitalize(str: string): string;
/**
* Remove common leading whitespace from a template string.
* Will also remove empty lines at the beginning and end.
* @category string
* @example
* ```ts
* const str = unindent`
* if (a) {
* b()
* }
* `
*/
declare function unindent(str: TemplateStringsArray | string): string;
declare const timestamp: () => number;
/**
* Call every function in an array
*/
declare function batchInvoke(functions: Nullable<Fn>[]): void;
/**
* Call the function
*/
declare function invoke(fn: Fn): void;
/**
* Pass the value through the callback, and return the value
*
* @example
* ```
* function createUser(name: string): User {
* return tap(new User, user => {
* user.name = name
* })
* }
* ```
*/
declare function tap<T>(value: T, callback: (value: T) => void): T;
/**
* Map key/value pairs for an object, and construct a new one
*
*
* @category Object
*
* Transform:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => [k.toString().toUpperCase(), v.toString()])
* // { A: '1', B: '2' }
* ```
*
* Swap key/value:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => [v, k])
* // { 1: 'a', 2: 'b' }
* ```
*
* Filter keys:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => k === 'a' ? undefined : [k, v])
* // { b: 2 }
* ```
*/
declare function objectMap<K extends string, V, NK extends string | number | symbol = K, NV = V>(obj: Record<K, V>, fn: (key: K, value: V) => [NK, NV] | undefined): Record<NK, NV>;
/**
* Type guard for any key, `k`.
* Marks `k` as a key of `T` if `k` is in `obj`.
*
* @category Object
* @param obj object to query for key `k`
* @param k key to check existence in `obj`
*/
declare function isKeyOf<T extends object>(obj: T, k: keyof any): k is keyof T;
/**
* Strict typed `Object.keys`
*
* @category Object
*/
declare function objectKeys<T extends object>(obj: T): (`${keyof T & undefined}` | `${keyof T & null}` | `${keyof T & string}` | `${keyof T & number}` | `${keyof T & false}` | `${keyof T & true}`)[];
/**
* Strict typed `Object.entries`
*
* @category Object
*/
declare function objectEntries<T extends object>(obj: T): [keyof T, T[keyof T]][];
/**
* Deep merge
*
* The first argument is the target object, the rest are the sources.
* The target object will be mutated and returned.
*
* @category Object
*/
declare function deepMerge<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S>;
/**
* Deep merge
*
* Differs from `deepMerge` in that it merges arrays instead of overriding them.
*
* The first argument is the target object, the rest are the sources.
* The target object will be mutated and returned.
*
* @category Object
*/
declare function deepMergeWithArray<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S>;
/**
* Create a new subset object by giving keys
*
* @category Object
*/
declare function objectPick<O extends object, T extends keyof O>(obj: O, keys: T[], omitUndefined?: boolean): Pick<O, T>;
/**
* Clear undefined fields from an object. It mutates the object
*
* @category Object
*/
declare function clearUndefined<T extends object>(obj: T): T;
/**
* Determines whether an object has a property with the specified name
*
* @see https://eslint.org/docs/rules/no-prototype-builtins
* @category Object
*/
declare function hasOwnProperty<T>(obj: T, v: PropertyKey): boolean;
interface SingletonPromiseReturn<T> {
(): Promise<T>;
/**
* Reset current staled promise.
* Await it to have proper shutdown.
*/
reset: () => Promise<void>;
}
/**
* Create singleton promise function
*
* @category Promise
*/
declare function createSingletonPromise<T>(fn: () => Promise<T>): SingletonPromiseReturn<T>;
/**
* Promised `setTimeout`
*
* @category Promise
*/
declare function sleep(ms: number, callback?: Fn<any>): Promise<void>;
/**
* Create a promise lock
*
* @category Promise
* @example
* ```
* const lock = createPromiseLock()
*
* lock.run(async () => {
* await doSomething()
* })
*
* // in anther context:
* await lock.wait() // it will wait all tasking finished
* ```
*/
declare function createPromiseLock(): {
run<T = void>(fn: () => Promise<T>): Promise<T>;
wait(): Promise<void>;
isWaiting(): boolean;
clear(): void;
};
/**
* Promise with `resolve` and `reject` methods of itself
*/
interface ControlledPromise<T = void> extends Promise<T> {
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
}
/**
* Return a Promise with `resolve` and `reject` methods
*
* @category Promise
* @example
* ```
* const promise = createControlledPromise()
*
* await promise
*
* // in anther context:
* promise.resolve(data)
* ```
*/
declare function createControlledPromise<T>(): ControlledPromise<T>;
interface CancelOptions {
upcomingOnly?: boolean;
}
interface Cancel {
cancel: (options?: CancelOptions) => void;
}
interface NoReturn<T extends (...args: any[]) => any> {
(...args: Parameters<T>): void;
}
interface ThrottleOptions {
noTrailing?: boolean;
noLeading?: boolean;
debounceMode?: boolean;
}
interface DebounceOptions {
atBegin?: boolean;
}
type throttle<T extends (...args: any[]) => any> = NoReturn<T> & Cancel;
/**
* Throttle execution of a function. Especially useful for rate limiting
* execution of handlers on events like resize and scroll.
*
* @param delay
* A zero-or-greater delay in milliseconds. For event callbacks, values around
* 100 or 250 (or even higher) are most useful.
*
* @param callback
* A function to be executed after delay milliseconds. The `this` context and
* all arguments are passed through, as-is, to `callback` when the
* throttled-function is executed.
*
* @param options
* An object to configure options.
*
* @param options.noTrailing
* Optional, defaults to false. If noTrailing is true, callback will only execute
* every `delay` milliseconds while the throttled-function is being called. If
* noTrailing is false or unspecified, callback will be executed one final time
* after the last throttled-function call. (After the throttled-function has not
* been called for `delay` milliseconds, the internal counter is reset)
*
* @param options.noLeading
* Optional, defaults to false. If noLeading is false, the first throttled-function
* call will execute callback immediately. If noLeading is true, the first the
* callback execution will be skipped. It should be noted that callback will never
* executed if both noLeading = true and noTrailing = true.
*
* @param options.debounceMode If `debounceMode` is true (at begin), schedule
* `callback` to execute after `delay` ms. If `debounceMode` is false (at end),
* schedule `callback` to execute after `delay` ms.
*
* @return
* A new, throttled, function.
*/
declare function throttle<T extends (...args: any[]) => any>(
delay: number,
callback: T,
options?: ThrottleOptions,
): throttle<T>;
type debounce<T extends (...args: any[]) => any> = NoReturn<T> & Cancel;
/**
* Debounce execution of a function. Debouncing, unlike throttling,
* guarantees that a function is only executed a single time, either at the
* very beginning of a series of calls, or at the very end.
*
* @param delay
* A zero-or-greater delay in milliseconds. For event callbacks, values around
* 100 or 250 (or even higher) are most useful.
*
* @param callback
* A function to be executed after delay milliseconds. The `this` context and
* all arguments are passed through, as-is, to `callback` when the
* debounced-function is executed.
*
* @param options
* An object to configure options.
*
* @param options.atBegin
* If atBegin is false or unspecified, callback will only be executed `delay`
* milliseconds after the last debounced-function call. If atBegin is true,
* callback will be executed only at the first debounced-function call. (After
* the throttled-function has not been called for `delay` milliseconds, the
* internal counter is reset).
*
* @return
* A new, debounced function.
*/
declare function debounce<T extends (...args: any[]) => any>(
delay: number,
callback: T,
options?: DebounceOptions,
): debounce<T>;
interface POptions {
/**
* How many promises are resolved at the same time.
*/
concurrency?: number | undefined;
}
declare class PInstance<T = any> extends Promise<Awaited<T>[]> {
items: Iterable<T>;
options?: POptions | undefined;
private promises;
get promise(): Promise<Awaited<T>[]>;
constructor(items?: Iterable<T>, options?: POptions | undefined);
add(...args: (T | Promise<T>)[]): void;
map<U>(fn: (value: Awaited<T>, index: number) => U): PInstance<Promise<U>>;
filter(fn: (value: Awaited<T>, index: number) => boolean | Promise<boolean>): PInstance<Promise<T>>;
forEach(fn: (value: Awaited<T>, index: number) => void): Promise<void>;
reduce<U>(fn: (previousValue: U, currentValue: Awaited<T>, currentIndex: number, array: Awaited<T>[]) => U, initialValue: U): Promise<U>;
clear(): void;
then(fn?: () => PromiseLike<any>): Promise<any>;
catch(fn?: (err: unknown) => PromiseLike<any>): Promise<any>;
finally(fn?: () => void): Promise<Awaited<T>[]>;
}
/**
* Utility for managing multiple promises.
*
* @see https://github.com/antfu/utils/tree/main/docs/p.md
* @category Promise
* @example
* ```
* import { p } from '@antfu/utils'
*
* const items = [1, 2, 3, 4, 5]
*
* await p(items)
* .map(async i => await multiply(i, 3))
* .filter(async i => await isEven(i))
* // [6, 12]
* ```
*/
declare function p<T = any>(items?: Iterable<T>, options?: POptions): PInstance<T>;
export { type ArgumentsType, type Arrayable, type Awaitable, type Constructor, type ControlledPromise, type DeepMerge, type ElementOf, type Fn, type MergeInsertions, type Nullable, type PartitionFilter, type SingletonPromiseReturn, type UnionToIntersection, assert, at, batchInvoke, capitalize, clamp, clampArrayRange, clearUndefined, createControlledPromise, createPromiseLock, createSingletonPromise, debounce, deepMerge, deepMergeWithArray, ensurePrefix, ensureSuffix, flattenArrayable, getTypeName, hasOwnProperty, invoke, isBoolean, isBrowser, isDate, isDeepEqual, isDef, isFunction, isKeyOf, isNull, isNumber, isObject, isRegExp, isString, isTruthy, isUndefined, isWindow, last, lerp, mergeArrayable, move, noNull, noop, notNullish, notUndefined, objectEntries, objectKeys, objectMap, objectPick, p, partition, randomStr, range, remap, remove, sample, shuffle, slash, sleep, sum, tap, template, throttle, timestamp, toArray, toString, unindent, uniq, uniqueBy };
+777
View File
@@ -0,0 +1,777 @@
function clamp(n, min, max) {
return Math.min(max, Math.max(min, n));
}
function sum(...args) {
return flattenArrayable(args).reduce((a, b) => a + b, 0);
}
function lerp(min, max, t) {
const interpolation = clamp(t, 0, 1);
return min + (max - min) * interpolation;
}
function remap(n, inMin, inMax, outMin, outMax) {
const interpolation = (n - inMin) / (inMax - inMin);
return lerp(outMin, outMax, interpolation);
}
function toArray(array) {
array = array ?? [];
return Array.isArray(array) ? array : [array];
}
function flattenArrayable(array) {
return toArray(array).flat(1);
}
function mergeArrayable(...args) {
return args.flatMap((i) => toArray(i));
}
function partition(array, ...filters) {
const result = Array.from({ length: filters.length + 1 }).fill(null).map(() => []);
array.forEach((e, idx, arr) => {
let i = 0;
for (const filter of filters) {
if (filter(e, idx, arr)) {
result[i].push(e);
return;
}
i += 1;
}
result[i].push(e);
});
return result;
}
function uniq(array) {
return Array.from(new Set(array));
}
function uniqueBy(array, equalFn) {
return array.reduce((acc, cur) => {
const index = acc.findIndex((item) => equalFn(cur, item));
if (index === -1)
acc.push(cur);
return acc;
}, []);
}
function last(array) {
return at(array, -1);
}
function remove(array, value) {
if (!array)
return false;
const index = array.indexOf(value);
if (index >= 0) {
array.splice(index, 1);
return true;
}
return false;
}
function at(array, index) {
const len = array.length;
if (!len)
return void 0;
if (index < 0)
index += len;
return array[index];
}
function range(...args) {
let start, stop, step;
if (args.length === 1) {
start = 0;
step = 1;
[stop] = args;
} else {
[start, stop, step = 1] = args;
}
const arr = [];
let current = start;
while (current < stop) {
arr.push(current);
current += step || 1;
}
return arr;
}
function move(arr, from, to) {
arr.splice(to, 0, arr.splice(from, 1)[0]);
return arr;
}
function clampArrayRange(n, arr) {
return clamp(n, 0, arr.length - 1);
}
function sample(arr, quantity) {
return Array.from({ length: quantity }, (_) => arr[Math.round(Math.random() * (arr.length - 1))]);
}
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function assert(condition, message) {
if (!condition)
throw new Error(message);
}
const toString = (v) => Object.prototype.toString.call(v);
function getTypeName(v) {
if (v === null)
return "null";
const type = toString(v).slice(8, -1).toLowerCase();
return typeof v === "object" || typeof v === "function" ? type : typeof v;
}
function noop() {
}
function isDeepEqual(value1, value2) {
const type1 = getTypeName(value1);
const type2 = getTypeName(value2);
if (type1 !== type2)
return false;
if (type1 === "array") {
if (value1.length !== value2.length)
return false;
return value1.every((item, i) => {
return isDeepEqual(item, value2[i]);
});
}
if (type1 === "object") {
const keyArr = Object.keys(value1);
if (keyArr.length !== Object.keys(value2).length)
return false;
return keyArr.every((key) => {
return isDeepEqual(value1[key], value2[key]);
});
}
return Object.is(value1, value2);
}
function notNullish(v) {
return v != null;
}
function noNull(v) {
return v !== null;
}
function notUndefined(v) {
return v !== void 0;
}
function isTruthy(v) {
return Boolean(v);
}
const isDef = (val) => typeof val !== "undefined";
const isBoolean = (val) => typeof val === "boolean";
const isFunction = (val) => typeof val === "function";
const isNumber = (val) => typeof val === "number";
const isString = (val) => typeof val === "string";
const isObject = (val) => toString(val) === "[object Object]";
const isUndefined = (val) => toString(val) === "[object Undefined]";
const isNull = (val) => toString(val) === "[object Null]";
const isRegExp = (val) => toString(val) === "[object RegExp]";
const isDate = (val) => toString(val) === "[object Date]";
const isWindow = (val) => typeof window !== "undefined" && toString(val) === "[object Window]";
const isBrowser = typeof window !== "undefined";
function slash(str) {
return str.replace(/\\/g, "/");
}
function ensurePrefix(prefix, str) {
if (!str.startsWith(prefix))
return prefix + str;
return str;
}
function ensureSuffix(suffix, str) {
if (!str.endsWith(suffix))
return str + suffix;
return str;
}
function template(str, ...args) {
const [firstArg, fallback] = args;
if (isObject(firstArg)) {
const vars = firstArg;
return str.replace(/{([\w\d]+)}/g, (_, key) => vars[key] || ((typeof fallback === "function" ? fallback(key) : fallback) ?? key));
} else {
return str.replace(/{(\d+)}/g, (_, key) => {
const index = Number(key);
if (Number.isNaN(index))
return key;
return args[index];
});
}
}
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
function randomStr(size = 16, dict = urlAlphabet) {
let id = "";
let i = size;
const len = dict.length;
while (i--)
id += dict[Math.random() * len | 0];
return id;
}
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1).toLowerCase();
}
const _reFullWs = /^\s*$/;
function unindent(str) {
const lines = (typeof str === "string" ? str : str[0]).split("\n");
const whitespaceLines = lines.map((line) => _reFullWs.test(line));
const commonIndent = lines.reduce((min, line, idx) => {
var _a;
if (whitespaceLines[idx])
return min;
const indent = (_a = line.match(/^\s*/)) == null ? void 0 : _a[0].length;
return indent === void 0 ? min : Math.min(min, indent);
}, Number.POSITIVE_INFINITY);
let emptyLinesHead = 0;
while (emptyLinesHead < lines.length && whitespaceLines[emptyLinesHead])
emptyLinesHead++;
let emptyLinesTail = 0;
while (emptyLinesTail < lines.length && whitespaceLines[lines.length - emptyLinesTail - 1])
emptyLinesTail++;
return lines.slice(emptyLinesHead, lines.length - emptyLinesTail).map((line) => line.slice(commonIndent)).join("\n");
}
const timestamp = () => +Date.now();
function batchInvoke(functions) {
functions.forEach((fn) => fn && fn());
}
function invoke(fn) {
return fn();
}
function tap(value, callback) {
callback(value);
return value;
}
function objectMap(obj, fn) {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => fn(k, v)).filter(notNullish)
);
}
function isKeyOf(obj, k) {
return k in obj;
}
function objectKeys(obj) {
return Object.keys(obj);
}
function objectEntries(obj) {
return Object.entries(obj);
}
function deepMerge(target, ...sources) {
if (!sources.length)
return target;
const source = sources.shift();
if (source === void 0)
return target;
if (isMergableObject(target) && isMergableObject(source)) {
objectKeys(source).forEach((key) => {
if (key === "__proto__" || key === "constructor" || key === "prototype")
return;
if (isMergableObject(source[key])) {
if (!target[key])
target[key] = {};
if (isMergableObject(target[key])) {
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
} else {
target[key] = source[key];
}
});
}
return deepMerge(target, ...sources);
}
function deepMergeWithArray(target, ...sources) {
if (!sources.length)
return target;
const source = sources.shift();
if (source === void 0)
return target;
if (Array.isArray(target) && Array.isArray(source))
target.push(...source);
if (isMergableObject(target) && isMergableObject(source)) {
objectKeys(source).forEach((key) => {
if (key === "__proto__" || key === "constructor" || key === "prototype")
return;
if (Array.isArray(source[key])) {
if (!target[key])
target[key] = [];
deepMergeWithArray(target[key], source[key]);
} else if (isMergableObject(source[key])) {
if (!target[key])
target[key] = {};
deepMergeWithArray(target[key], source[key]);
} else {
target[key] = source[key];
}
});
}
return deepMergeWithArray(target, ...sources);
}
function isMergableObject(item) {
return isObject(item) && !Array.isArray(item);
}
function objectPick(obj, keys, omitUndefined = false) {
return keys.reduce((n, k) => {
if (k in obj) {
if (!omitUndefined || obj[k] !== void 0)
n[k] = obj[k];
}
return n;
}, {});
}
function clearUndefined(obj) {
Object.keys(obj).forEach((key) => obj[key] === void 0 ? delete obj[key] : {});
return obj;
}
function hasOwnProperty(obj, v) {
if (obj == null)
return false;
return Object.prototype.hasOwnProperty.call(obj, v);
}
function createSingletonPromise(fn) {
let _promise;
function wrapper() {
if (!_promise)
_promise = fn();
return _promise;
}
wrapper.reset = async () => {
const _prev = _promise;
_promise = void 0;
if (_prev)
await _prev;
};
return wrapper;
}
function sleep(ms, callback) {
return new Promise(
(resolve) => setTimeout(async () => {
await (callback == null ? void 0 : callback());
resolve();
}, ms)
);
}
function createPromiseLock() {
const locks = [];
return {
async run(fn) {
const p = fn();
locks.push(p);
try {
return await p;
} finally {
remove(locks, p);
}
},
async wait() {
await Promise.allSettled(locks);
},
isWaiting() {
return Boolean(locks.length);
},
clear() {
locks.length = 0;
}
};
}
function createControlledPromise() {
let resolve, reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
}
/* eslint-disable no-undefined,no-param-reassign,no-shadow */
/**
* Throttle execution of a function. Especially useful for rate limiting
* execution of handlers on events like resize and scroll.
*
* @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher)
* are most useful.
* @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through,
* as-is, to `callback` when the throttled-function is executed.
* @param {object} [options] - An object to configure options.
* @param {boolean} [options.noTrailing] - Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds
* while the throttled-function is being called. If noTrailing is false or unspecified, callback will be executed
* one final time after the last throttled-function call. (After the throttled-function has not been called for
* `delay` milliseconds, the internal counter is reset).
* @param {boolean} [options.noLeading] - Optional, defaults to false. If noLeading is false, the first throttled-function call will execute callback
* immediately. If noLeading is true, the first the callback execution will be skipped. It should be noted that
* callback will never executed if both noLeading = true and noTrailing = true.
* @param {boolean} [options.debounceMode] - If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is
* false (at end), schedule `callback` to execute after `delay` ms.
*
* @returns {Function} A new, throttled, function.
*/
function throttle (delay, callback, options) {
var _ref = options || {},
_ref$noTrailing = _ref.noTrailing,
noTrailing = _ref$noTrailing === void 0 ? false : _ref$noTrailing,
_ref$noLeading = _ref.noLeading,
noLeading = _ref$noLeading === void 0 ? false : _ref$noLeading,
_ref$debounceMode = _ref.debounceMode,
debounceMode = _ref$debounceMode === void 0 ? undefined : _ref$debounceMode;
/*
* After wrapper has stopped being called, this timeout ensures that
* `callback` is executed at the proper times in `throttle` and `end`
* debounce modes.
*/
var timeoutID;
var cancelled = false; // Keep track of the last time `callback` was executed.
var lastExec = 0; // Function to clear existing timeout
function clearExistingTimeout() {
if (timeoutID) {
clearTimeout(timeoutID);
}
} // Function to cancel next exec
function cancel(options) {
var _ref2 = options || {},
_ref2$upcomingOnly = _ref2.upcomingOnly,
upcomingOnly = _ref2$upcomingOnly === void 0 ? false : _ref2$upcomingOnly;
clearExistingTimeout();
cancelled = !upcomingOnly;
}
/*
* The `wrapper` function encapsulates all of the throttling / debouncing
* functionality and when executed will limit the rate at which `callback`
* is executed.
*/
function wrapper() {
for (var _len = arguments.length, arguments_ = new Array(_len), _key = 0; _key < _len; _key++) {
arguments_[_key] = arguments[_key];
}
var self = this;
var elapsed = Date.now() - lastExec;
if (cancelled) {
return;
} // Execute `callback` and update the `lastExec` timestamp.
function exec() {
lastExec = Date.now();
callback.apply(self, arguments_);
}
/*
* If `debounceMode` is true (at begin) this is used to clear the flag
* to allow future `callback` executions.
*/
function clear() {
timeoutID = undefined;
}
if (!noLeading && debounceMode && !timeoutID) {
/*
* Since `wrapper` is being called for the first time and
* `debounceMode` is true (at begin), execute `callback`
* and noLeading != true.
*/
exec();
}
clearExistingTimeout();
if (debounceMode === undefined && elapsed > delay) {
if (noLeading) {
/*
* In throttle mode with noLeading, if `delay` time has
* been exceeded, update `lastExec` and schedule `callback`
* to execute after `delay` ms.
*/
lastExec = Date.now();
if (!noTrailing) {
timeoutID = setTimeout(debounceMode ? clear : exec, delay);
}
} else {
/*
* In throttle mode without noLeading, if `delay` time has been exceeded, execute
* `callback`.
*/
exec();
}
} else if (noTrailing !== true) {
/*
* In trailing throttle mode, since `delay` time has not been
* exceeded, schedule `callback` to execute `delay` ms after most
* recent execution.
*
* If `debounceMode` is true (at begin), schedule `clear` to execute
* after `delay` ms.
*
* If `debounceMode` is false (at end), schedule `callback` to
* execute after `delay` ms.
*/
timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);
}
}
wrapper.cancel = cancel; // Return the wrapper function.
return wrapper;
}
/* eslint-disable no-undefined */
/**
* Debounce execution of a function. Debouncing, unlike throttling,
* guarantees that a function is only executed a single time, either at the
* very beginning of a series of calls, or at the very end.
*
* @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.
* @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,
* to `callback` when the debounced-function is executed.
* @param {object} [options] - An object to configure options.
* @param {boolean} [options.atBegin] - Optional, defaults to false. If atBegin is false or unspecified, callback will only be executed `delay` milliseconds
* after the last debounced-function call. If atBegin is true, callback will be executed only at the first debounced-function call.
* (After the throttled-function has not been called for `delay` milliseconds, the internal counter is reset).
*
* @returns {Function} A new, debounced function.
*/
function debounce (delay, callback, options) {
var _ref = options || {},
_ref$atBegin = _ref.atBegin,
atBegin = _ref$atBegin === void 0 ? false : _ref$atBegin;
return throttle(delay, callback, {
debounceMode: atBegin !== false
});
}
/*
How it works:
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
*/
class Node {
value;
next;
constructor(value) {
this.value = value;
}
}
class Queue {
#head;
#tail;
#size;
constructor() {
this.clear();
}
enqueue(value) {
const node = new Node(value);
if (this.#head) {
this.#tail.next = node;
this.#tail = node;
} else {
this.#head = node;
this.#tail = node;
}
this.#size++;
}
dequeue() {
const current = this.#head;
if (!current) {
return;
}
this.#head = this.#head.next;
this.#size--;
return current.value;
}
clear() {
this.#head = undefined;
this.#tail = undefined;
this.#size = 0;
}
get size() {
return this.#size;
}
* [Symbol.iterator]() {
let current = this.#head;
while (current) {
yield current.value;
current = current.next;
}
}
}
const AsyncResource = {
bind(fn, _type, thisArg) {
return fn.bind(thisArg);
},
};
function pLimit(concurrency) {
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
throw new TypeError('Expected `concurrency` to be a number from 1 and up');
}
const queue = new Queue();
let activeCount = 0;
const next = () => {
activeCount--;
if (queue.size > 0) {
queue.dequeue()();
}
};
const run = async (function_, resolve, arguments_) => {
activeCount++;
const result = (async () => function_(...arguments_))();
resolve(result);
try {
await result;
} catch {}
next();
};
const enqueue = (function_, resolve, arguments_) => {
queue.enqueue(
AsyncResource.bind(run.bind(undefined, function_, resolve, arguments_)),
);
(async () => {
// This function needs to wait until the next microtask before comparing
// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously
// when the run function is dequeued and called. The comparison in the if-statement
// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.
await Promise.resolve();
if (activeCount < concurrency && queue.size > 0) {
queue.dequeue()();
}
})();
};
const generator = (function_, ...arguments_) => new Promise(resolve => {
enqueue(function_, resolve, arguments_);
});
Object.defineProperties(generator, {
activeCount: {
get: () => activeCount,
},
pendingCount: {
get: () => queue.size,
},
clearQueue: {
value() {
queue.clear();
},
},
});
return generator;
}
const VOID = Symbol("p-void");
class PInstance extends Promise {
constructor(items = [], options) {
super(() => {
});
this.items = items;
this.options = options;
this.promises = /* @__PURE__ */ new Set();
}
get promise() {
var _a;
let batch;
const items = [...Array.from(this.items), ...Array.from(this.promises)];
if ((_a = this.options) == null ? void 0 : _a.concurrency) {
const limit = pLimit(this.options.concurrency);
batch = Promise.all(items.map((p2) => limit(() => p2)));
} else {
batch = Promise.all(items);
}
return batch.then((l) => l.filter((i) => i !== VOID));
}
add(...args) {
args.forEach((i) => {
this.promises.add(i);
});
}
map(fn) {
return new PInstance(
Array.from(this.items).map(async (i, idx) => {
const v = await i;
if (v === VOID)
return VOID;
return fn(v, idx);
}),
this.options
);
}
filter(fn) {
return new PInstance(
Array.from(this.items).map(async (i, idx) => {
const v = await i;
const r = await fn(v, idx);
if (!r)
return VOID;
return v;
}),
this.options
);
}
forEach(fn) {
return this.map(fn).then();
}
reduce(fn, initialValue) {
return this.promise.then((array) => array.reduce(fn, initialValue));
}
clear() {
this.promises.clear();
}
then(fn) {
const p2 = this.promise;
if (fn)
return p2.then(fn);
else
return p2;
}
catch(fn) {
return this.promise.catch(fn);
}
finally(fn) {
return this.promise.finally(fn);
}
}
function p(items, options) {
return new PInstance(items, options);
}
export { assert, at, batchInvoke, capitalize, clamp, clampArrayRange, clearUndefined, createControlledPromise, createPromiseLock, createSingletonPromise, debounce, deepMerge, deepMergeWithArray, ensurePrefix, ensureSuffix, flattenArrayable, getTypeName, hasOwnProperty, invoke, isBoolean, isBrowser, isDate, isDeepEqual, isDef, isFunction, isKeyOf, isNull, isNumber, isObject, isRegExp, isString, isTruthy, isUndefined, isWindow, last, lerp, mergeArrayable, move, noNull, noop, notNullish, notUndefined, objectEntries, objectKeys, objectMap, objectPick, p, partition, randomStr, range, remap, remove, sample, shuffle, slash, sleep, sum, tap, template, throttle, timestamp, toArray, toString, unindent, uniq, uniqueBy };
+67
View File
@@ -0,0 +1,67 @@
{
"name": "@antfu/utils",
"type": "module",
"version": "0.7.10",
"packageManager": "pnpm@9.1.0",
"description": "Opinionated collection of common JavaScript / TypeScript utils by @antfu",
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
"license": "MIT",
"funding": "https://github.com/sponsors/antfu",
"homepage": "https://github.com/antfu/utils#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/antfu/utils.git"
},
"bugs": {
"url": "https://github.com/antfu/utils/issues"
},
"keywords": [
"utils"
],
"sideEffects": false,
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "rollup -c",
"dev": "nr build --watch",
"lint": "eslint .",
"lint-fix": "nr lint --fix",
"prepublishOnly": "npm run build",
"release": "bumpp --commit --push --tag && npm publish",
"start": "esno src/index.ts",
"typecheck": "tsc --noEmit",
"test": "vitest"
},
"devDependencies": {
"@antfu/eslint-config": "^2.16.3",
"@antfu/ni": "^0.21.12",
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/node": "^20.12.10",
"@types/throttle-debounce": "^5.0.2",
"bumpp": "^9.4.1",
"eslint": "npm:eslint-ts-patch@8.55.0-1",
"eslint-ts-patch": "8.55.0-1",
"esno": "^4.7.0",
"p-limit": "^5.0.0",
"rollup": "^4.17.2",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-esbuild": "^6.1.1",
"throttle-debounce": "5.0.0",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"vitest": "^1.6.0"
}
}
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
# @babel/helper-string-parser
> A utility package to parse strings
See our website [@babel/helper-string-parser](https://babeljs.io/docs/babel-helper-string-parser) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-string-parser
```
or using yarn:
```sh
yarn add @babel/helper-string-parser
```
@@ -0,0 +1,295 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.readCodePoint = readCodePoint;
exports.readInt = readInt;
exports.readStringContents = readStringContents;
var _isDigit = function isDigit(code) {
return code >= 48 && code <= 57;
};
const forbiddenNumericSeparatorSiblings = {
decBinOct: new Set([46, 66, 69, 79, 95, 98, 101, 111]),
hex: new Set([46, 88, 95, 120])
};
const isAllowedNumericSeparatorSibling = {
bin: ch => ch === 48 || ch === 49,
oct: ch => ch >= 48 && ch <= 55,
dec: ch => ch >= 48 && ch <= 57,
hex: ch => ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch <= 102
};
function readStringContents(type, input, pos, lineStart, curLine, errors) {
const initialPos = pos;
const initialLineStart = lineStart;
const initialCurLine = curLine;
let out = "";
let firstInvalidLoc = null;
let chunkStart = pos;
const {
length
} = input;
for (;;) {
if (pos >= length) {
errors.unterminated(initialPos, initialLineStart, initialCurLine);
out += input.slice(chunkStart, pos);
break;
}
const ch = input.charCodeAt(pos);
if (isStringEnd(type, ch, input, pos)) {
out += input.slice(chunkStart, pos);
break;
}
if (ch === 92) {
out += input.slice(chunkStart, pos);
const res = readEscapedChar(input, pos, lineStart, curLine, type === "template", errors);
if (res.ch === null && !firstInvalidLoc) {
firstInvalidLoc = {
pos,
lineStart,
curLine
};
} else {
out += res.ch;
}
({
pos,
lineStart,
curLine
} = res);
chunkStart = pos;
} else if (ch === 8232 || ch === 8233) {
++pos;
++curLine;
lineStart = pos;
} else if (ch === 10 || ch === 13) {
if (type === "template") {
out += input.slice(chunkStart, pos) + "\n";
++pos;
if (ch === 13 && input.charCodeAt(pos) === 10) {
++pos;
}
++curLine;
chunkStart = lineStart = pos;
} else {
errors.unterminated(initialPos, initialLineStart, initialCurLine);
}
} else {
++pos;
}
}
return {
pos,
str: out,
firstInvalidLoc,
lineStart,
curLine,
containsInvalid: !!firstInvalidLoc
};
}
function isStringEnd(type, ch, input, pos) {
if (type === "template") {
return ch === 96 || ch === 36 && input.charCodeAt(pos + 1) === 123;
}
return ch === (type === "double" ? 34 : 39);
}
function readEscapedChar(input, pos, lineStart, curLine, inTemplate, errors) {
const throwOnInvalid = !inTemplate;
pos++;
const res = ch => ({
pos,
ch,
lineStart,
curLine
});
const ch = input.charCodeAt(pos++);
switch (ch) {
case 110:
return res("\n");
case 114:
return res("\r");
case 120:
{
let code;
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, 2, false, throwOnInvalid, errors));
return res(code === null ? null : String.fromCharCode(code));
}
case 117:
{
let code;
({
code,
pos
} = readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors));
return res(code === null ? null : String.fromCodePoint(code));
}
case 116:
return res("\t");
case 98:
return res("\b");
case 118:
return res("\u000b");
case 102:
return res("\f");
case 13:
if (input.charCodeAt(pos) === 10) {
++pos;
}
case 10:
lineStart = pos;
++curLine;
case 8232:
case 8233:
return res("");
case 56:
case 57:
if (inTemplate) {
return res(null);
} else {
errors.strictNumericEscape(pos - 1, lineStart, curLine);
}
default:
if (ch >= 48 && ch <= 55) {
const startPos = pos - 1;
const match = /^[0-7]+/.exec(input.slice(startPos, pos + 2));
let octalStr = match[0];
let octal = parseInt(octalStr, 8);
if (octal > 255) {
octalStr = octalStr.slice(0, -1);
octal = parseInt(octalStr, 8);
}
pos += octalStr.length - 1;
const next = input.charCodeAt(pos);
if (octalStr !== "0" || next === 56 || next === 57) {
if (inTemplate) {
return res(null);
} else {
errors.strictNumericEscape(startPos, lineStart, curLine);
}
}
return res(String.fromCharCode(octal));
}
return res(String.fromCharCode(ch));
}
}
function readHexChar(input, pos, lineStart, curLine, len, forceLen, throwOnInvalid, errors) {
const initialPos = pos;
let n;
({
n,
pos
} = readInt(input, pos, lineStart, curLine, 16, len, forceLen, false, errors, !throwOnInvalid));
if (n === null) {
if (throwOnInvalid) {
errors.invalidEscapeSequence(initialPos, lineStart, curLine);
} else {
pos = initialPos - 1;
}
}
return {
code: n,
pos
};
}
function readInt(input, pos, lineStart, curLine, radix, len, forceLen, allowNumSeparator, errors, bailOnError) {
const start = pos;
const forbiddenSiblings = radix === 16 ? forbiddenNumericSeparatorSiblings.hex : forbiddenNumericSeparatorSiblings.decBinOct;
const isAllowedSibling = radix === 16 ? isAllowedNumericSeparatorSibling.hex : radix === 10 ? isAllowedNumericSeparatorSibling.dec : radix === 8 ? isAllowedNumericSeparatorSibling.oct : isAllowedNumericSeparatorSibling.bin;
let invalid = false;
let total = 0;
for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) {
const code = input.charCodeAt(pos);
let val;
if (code === 95 && allowNumSeparator !== "bail") {
const prev = input.charCodeAt(pos - 1);
const next = input.charCodeAt(pos + 1);
if (!allowNumSeparator) {
if (bailOnError) return {
n: null,
pos
};
errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine);
} else if (Number.isNaN(next) || !isAllowedSibling(next) || forbiddenSiblings.has(prev) || forbiddenSiblings.has(next)) {
if (bailOnError) return {
n: null,
pos
};
errors.unexpectedNumericSeparator(pos, lineStart, curLine);
}
++pos;
continue;
}
if (code >= 97) {
val = code - 97 + 10;
} else if (code >= 65) {
val = code - 65 + 10;
} else if (_isDigit(code)) {
val = code - 48;
} else {
val = Infinity;
}
if (val >= radix) {
if (val <= 9 && bailOnError) {
return {
n: null,
pos
};
} else if (val <= 9 && errors.invalidDigit(pos, lineStart, curLine, radix)) {
val = 0;
} else if (forceLen) {
val = 0;
invalid = true;
} else {
break;
}
}
++pos;
total = total * radix + val;
}
if (pos === start || len != null && pos - start !== len || invalid) {
return {
n: null,
pos
};
}
return {
n: total,
pos
};
}
function readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors) {
const ch = input.charCodeAt(pos);
let code;
if (ch === 123) {
++pos;
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, input.indexOf("}", pos) - pos, true, throwOnInvalid, errors));
++pos;
if (code !== null && code > 0x10ffff) {
if (throwOnInvalid) {
errors.invalidCodePoint(pos, lineStart, curLine);
} else {
return {
code: null,
pos
};
}
}
} else {
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, 4, false, throwOnInvalid, errors));
}
return {
code,
pos
};
}
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,31 @@
{
"name": "@babel/helper-string-parser",
"version": "7.27.1",
"description": "A utility package to parse strings",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-helper-string-parser"
},
"homepage": "https://babel.dev/docs/en/next/babel-helper-string-parser",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"devDependencies": {
"charcodes": "^0.2.0"
},
"engines": {
"node": ">=6.9.0"
},
"author": "The Babel Team (https://babel.dev/team)",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
},
"./package.json": "./package.json"
},
"type": "commonjs"
}
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
# @babel/helper-validator-identifier
> Validate identifier/keywords name
See our website [@babel/helper-validator-identifier](https://babeljs.io/docs/babel-helper-validator-identifier) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-validator-identifier
```
or using yarn:
```sh
yarn add @babel/helper-validator-identifier
```
@@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isIdentifierChar = isIdentifierChar;
exports.isIdentifierName = isIdentifierName;
exports.isIdentifierStart = isIdentifierStart;
let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u0870-\u0887\u0889-\u088f\u08a0-\u08c9\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c5c\u0c5d\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cdc-\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u1711\u171f-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4c\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c8a\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7dc\ua7f1-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
let nonASCIIidentifierChars = "\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u0897-\u089f\u08ca-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3c\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0cf3\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ece\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u180f-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf-\u1add\u1ae0-\u1aeb\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\u30fb\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f\uff65";
const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
nonASCIIidentifierStartChars = nonASCIIidentifierChars = null;
const astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 7, 25, 39, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 5, 57, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 24, 43, 261, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 33, 24, 3, 24, 45, 74, 6, 0, 67, 12, 65, 1, 2, 0, 15, 4, 10, 7381, 42, 31, 98, 114, 8702, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 208, 30, 2, 2, 2, 1, 2, 6, 3, 4, 10, 1, 225, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4381, 3, 5773, 3, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 8489];
const astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 78, 5, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 199, 7, 137, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 55, 9, 266, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 233, 0, 3, 0, 8, 1, 6, 0, 475, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
function isInAstralSet(code, set) {
let pos = 0x10000;
for (let i = 0, length = set.length; i < length; i += 2) {
pos += set[i];
if (pos > code) return false;
pos += set[i + 1];
if (pos >= code) return true;
}
return false;
}
function isIdentifierStart(code) {
if (code < 65) return code === 36;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes);
}
function isIdentifierChar(code) {
if (code < 48) return code === 36;
if (code < 58) return true;
if (code < 65) return false;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
}
function isIdentifierName(name) {
let isFirst = true;
for (let i = 0; i < name.length; i++) {
let cp = name.charCodeAt(i);
if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) {
const trail = name.charCodeAt(++i);
if ((trail & 0xfc00) === 0xdc00) {
cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff);
}
}
if (isFirst) {
isFirst = false;
if (!isIdentifierStart(cp)) {
return false;
}
} else if (!isIdentifierChar(cp)) {
return false;
}
}
return !isFirst;
}
//# sourceMappingURL=identifier.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "isIdentifierChar", {
enumerable: true,
get: function () {
return _identifier.isIdentifierChar;
}
});
Object.defineProperty(exports, "isIdentifierName", {
enumerable: true,
get: function () {
return _identifier.isIdentifierName;
}
});
Object.defineProperty(exports, "isIdentifierStart", {
enumerable: true,
get: function () {
return _identifier.isIdentifierStart;
}
});
Object.defineProperty(exports, "isKeyword", {
enumerable: true,
get: function () {
return _keyword.isKeyword;
}
});
Object.defineProperty(exports, "isReservedWord", {
enumerable: true,
get: function () {
return _keyword.isReservedWord;
}
});
Object.defineProperty(exports, "isStrictBindOnlyReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictBindOnlyReservedWord;
}
});
Object.defineProperty(exports, "isStrictBindReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictBindReservedWord;
}
});
Object.defineProperty(exports, "isStrictReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictReservedWord;
}
});
var _identifier = require("./identifier.js");
var _keyword = require("./keyword.js");
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_identifier","require","_keyword"],"sources":["../src/index.ts"],"sourcesContent":["export {\n isIdentifierName,\n isIdentifierChar,\n isIdentifierStart,\n} from \"./identifier.ts\";\nexport {\n isReservedWord,\n isStrictBindOnlyReservedWord,\n isStrictBindReservedWord,\n isStrictReservedWord,\n isKeyword,\n} from \"./keyword.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AAKA,IAAAC,QAAA,GAAAD,OAAA","ignoreList":[]}
@@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isKeyword = isKeyword;
exports.isReservedWord = isReservedWord;
exports.isStrictBindOnlyReservedWord = isStrictBindOnlyReservedWord;
exports.isStrictBindReservedWord = isStrictBindReservedWord;
exports.isStrictReservedWord = isStrictReservedWord;
const reservedWords = {
keyword: ["break", "case", "catch", "continue", "debugger", "default", "do", "else", "finally", "for", "function", "if", "return", "switch", "throw", "try", "var", "const", "while", "with", "new", "this", "super", "class", "extends", "export", "import", "null", "true", "false", "in", "instanceof", "typeof", "void", "delete"],
strict: ["implements", "interface", "let", "package", "private", "protected", "public", "static", "yield"],
strictBind: ["eval", "arguments"]
};
const keywords = new Set(reservedWords.keyword);
const reservedWordsStrictSet = new Set(reservedWords.strict);
const reservedWordsStrictBindSet = new Set(reservedWords.strictBind);
function isReservedWord(word, inModule) {
return inModule && word === "await" || word === "enum";
}
function isStrictReservedWord(word, inModule) {
return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);
}
function isStrictBindOnlyReservedWord(word) {
return reservedWordsStrictBindSet.has(word);
}
function isStrictBindReservedWord(word, inModule) {
return isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word);
}
function isKeyword(word) {
return keywords.has(word);
}
//# sourceMappingURL=keyword.js.map
@@ -0,0 +1 @@
{"version":3,"names":["reservedWords","keyword","strict","strictBind","keywords","Set","reservedWordsStrictSet","reservedWordsStrictBindSet","isReservedWord","word","inModule","isStrictReservedWord","has","isStrictBindOnlyReservedWord","isStrictBindReservedWord","isKeyword"],"sources":["../src/keyword.ts"],"sourcesContent":["const reservedWords = {\n keyword: [\n \"break\",\n \"case\",\n \"catch\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"do\",\n \"else\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"return\",\n \"switch\",\n \"throw\",\n \"try\",\n \"var\",\n \"const\",\n \"while\",\n \"with\",\n \"new\",\n \"this\",\n \"super\",\n \"class\",\n \"extends\",\n \"export\",\n \"import\",\n \"null\",\n \"true\",\n \"false\",\n \"in\",\n \"instanceof\",\n \"typeof\",\n \"void\",\n \"delete\",\n ],\n strict: [\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n ],\n strictBind: [\"eval\", \"arguments\"],\n};\nconst keywords = new Set(reservedWords.keyword);\nconst reservedWordsStrictSet = new Set(reservedWords.strict);\nconst reservedWordsStrictBindSet = new Set(reservedWords.strictBind);\n\n/**\n * Checks if word is a reserved word in non-strict mode\n */\nexport function isReservedWord(word: string, inModule: boolean): boolean {\n return (inModule && word === \"await\") || word === \"enum\";\n}\n\n/**\n * Checks if word is a reserved word in non-binding strict mode\n *\n * Includes non-strict reserved words\n */\nexport function isStrictReservedWord(word: string, inModule: boolean): boolean {\n return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode, but it is allowed as\n * a normal identifier.\n */\nexport function isStrictBindOnlyReservedWord(word: string): boolean {\n return reservedWordsStrictBindSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode\n *\n * Includes non-strict reserved words and non-binding strict reserved words\n */\nexport function isStrictBindReservedWord(\n word: string,\n inModule: boolean,\n): boolean {\n return (\n isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word)\n );\n}\n\nexport function isKeyword(word: string): boolean {\n return keywords.has(word);\n}\n"],"mappings":";;;;;;;;;;AAAA,MAAMA,aAAa,GAAG;EACpBC,OAAO,EAAE,CACP,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,UAAU,EACV,SAAS,EACT,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,QAAQ,CACT;EACDC,MAAM,EAAE,CACN,YAAY,EACZ,WAAW,EACX,KAAK,EACL,SAAS,EACT,SAAS,EACT,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,OAAO,CACR;EACDC,UAAU,EAAE,CAAC,MAAM,EAAE,WAAW;AAClC,CAAC;AACD,MAAMC,QAAQ,GAAG,IAAIC,GAAG,CAACL,aAAa,CAACC,OAAO,CAAC;AAC/C,MAAMK,sBAAsB,GAAG,IAAID,GAAG,CAACL,aAAa,CAACE,MAAM,CAAC;AAC5D,MAAMK,0BAA0B,GAAG,IAAIF,GAAG,CAACL,aAAa,CAACG,UAAU,CAAC;AAK7D,SAASK,cAAcA,CAACC,IAAY,EAAEC,QAAiB,EAAW;EACvE,OAAQA,QAAQ,IAAID,IAAI,KAAK,OAAO,IAAKA,IAAI,KAAK,MAAM;AAC1D;AAOO,SAASE,oBAAoBA,CAACF,IAAY,EAAEC,QAAiB,EAAW;EAC7E,OAAOF,cAAc,CAACC,IAAI,EAAEC,QAAQ,CAAC,IAAIJ,sBAAsB,CAACM,GAAG,CAACH,IAAI,CAAC;AAC3E;AAMO,SAASI,4BAA4BA,CAACJ,IAAY,EAAW;EAClE,OAAOF,0BAA0B,CAACK,GAAG,CAACH,IAAI,CAAC;AAC7C;AAOO,SAASK,wBAAwBA,CACtCL,IAAY,EACZC,QAAiB,EACR;EACT,OACEC,oBAAoB,CAACF,IAAI,EAAEC,QAAQ,CAAC,IAAIG,4BAA4B,CAACJ,IAAI,CAAC;AAE9E;AAEO,SAASM,SAASA,CAACN,IAAY,EAAW;EAC/C,OAAOL,QAAQ,CAACQ,GAAG,CAACH,IAAI,CAAC;AAC3B","ignoreList":[]}
@@ -0,0 +1,31 @@
{
"name": "@babel/helper-validator-identifier",
"version": "7.28.5",
"description": "Validate identifier/keywords name",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-helper-validator-identifier"
},
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
},
"./package.json": "./package.json"
},
"devDependencies": {
"@unicode/unicode-17.0.0": "^1.6.10",
"charcodes": "^0.2.0"
},
"engines": {
"node": ">=6.9.0"
},
"author": "The Babel Team (https://babel.dev/team)",
"type": "commonjs"
}
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
Copyright (C) 2012-2014 by various contributors (see AUTHORS)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# @babel/parser
> A JavaScript parser
See our website [@babel/parser](https://babeljs.io/docs/babel-parser) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20parser%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/parser
```
or using yarn:
```sh
yarn add @babel/parser --dev
```
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env node
/* eslint-disable no-var, unicorn/prefer-node-protocol */
var parser = require("..");
var fs = require("fs");
var filename = process.argv[2];
if (!filename) {
console.error("no filename specified");
} else {
var file = fs.readFileSync(filename, "utf8");
var ast = parser.parse(file);
console.log(JSON.stringify(ast, null, " "));
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+50
View File
@@ -0,0 +1,50 @@
{
"name": "@babel/parser",
"version": "7.29.2",
"description": "A JavaScript parser",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-parser",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A+parser+%28babylon%29%22+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"keywords": [
"babel",
"javascript",
"parser",
"tc39",
"ecmascript",
"@babel/parser"
],
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-parser"
},
"main": "./lib/index.js",
"types": "./typings/babel-parser.d.ts",
"files": [
"bin",
"lib",
"typings/babel-parser.d.ts",
"index.cjs"
],
"engines": {
"node": ">=6.0.0"
},
"# dependencies": "This package doesn't actually have runtime dependencies. @babel/types is only needed for type definitions.",
"dependencies": {
"@babel/types": "^7.29.0"
},
"devDependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/helper-check-duplicate-nodes": "^7.28.6",
"@babel/helper-fixtures": "^7.28.6",
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5",
"charcodes": "^0.2.0"
},
"bin": "./bin/babel-parser.js",
"type": "commonjs"
}
@@ -0,0 +1,262 @@
// This file is auto-generated! Do not modify it directly.
// Run `yarn gulp bundle-dts` to re-generate it.
/* eslint-disable @typescript-eslint/consistent-type-imports, @typescript-eslint/no-redundant-type-constituents */
import { File, Expression } from '@babel/types';
declare class Position {
line: number;
column: number;
index: number;
constructor(line: number, col: number, index: number);
}
type SyntaxPlugin = "flow" | "typescript" | "jsx" | "pipelineOperator" | "placeholders";
type ParseErrorCode = "BABEL_PARSER_SYNTAX_ERROR" | "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED";
interface ParseErrorSpecification<ErrorDetails> {
code: ParseErrorCode;
reasonCode: string;
syntaxPlugin?: SyntaxPlugin;
missingPlugin?: string | string[];
loc: Position;
details: ErrorDetails;
pos: number;
}
type ParseError$1<ErrorDetails> = SyntaxError & ParseErrorSpecification<ErrorDetails>;
type BABEL_8_BREAKING = false;
type IF_BABEL_7<V> = false extends BABEL_8_BREAKING ? V : never;
type Plugin$1 =
| "asyncDoExpressions"
| IF_BABEL_7<"asyncGenerators">
| IF_BABEL_7<"bigInt">
| IF_BABEL_7<"classPrivateMethods">
| IF_BABEL_7<"classPrivateProperties">
| IF_BABEL_7<"classProperties">
| IF_BABEL_7<"classStaticBlock">
| IF_BABEL_7<"decimal">
| "decorators-legacy"
| "deferredImportEvaluation"
| "decoratorAutoAccessors"
| "destructuringPrivate"
| IF_BABEL_7<"deprecatedImportAssert">
| "doExpressions"
| IF_BABEL_7<"dynamicImport">
| IF_BABEL_7<"explicitResourceManagement">
| "exportDefaultFrom"
| IF_BABEL_7<"exportNamespaceFrom">
| "flow"
| "flowComments"
| "functionBind"
| "functionSent"
| "importMeta"
| "jsx"
| IF_BABEL_7<"jsonStrings">
| IF_BABEL_7<"logicalAssignment">
| IF_BABEL_7<"importAssertions">
| IF_BABEL_7<"importReflection">
| "moduleBlocks"
| IF_BABEL_7<"moduleStringNames">
| IF_BABEL_7<"nullishCoalescingOperator">
| IF_BABEL_7<"numericSeparator">
| IF_BABEL_7<"objectRestSpread">
| IF_BABEL_7<"optionalCatchBinding">
| IF_BABEL_7<"optionalChaining">
| "partialApplication"
| "placeholders"
| IF_BABEL_7<"privateIn">
| IF_BABEL_7<"regexpUnicodeSets">
| "sourcePhaseImports"
| "throwExpressions"
| IF_BABEL_7<"topLevelAwait">
| "v8intrinsic"
| ParserPluginWithOptions[0];
type ParserPluginWithOptions =
| ["decorators", DecoratorsPluginOptions]
| ["discardBinding", { syntaxType: "void" }]
| ["estree", { classFeatures?: boolean }]
| IF_BABEL_7<["importAttributes", { deprecatedAssertSyntax: boolean }]>
| IF_BABEL_7<["moduleAttributes", { version: "may-2020" }]>
| ["optionalChainingAssign", { version: "2023-07" }]
| ["pipelineOperator", PipelineOperatorPluginOptions]
| ["recordAndTuple", RecordAndTuplePluginOptions]
| ["flow", FlowPluginOptions]
| ["typescript", TypeScriptPluginOptions];
type PluginConfig = Plugin$1 | ParserPluginWithOptions;
interface DecoratorsPluginOptions {
decoratorsBeforeExport?: boolean;
allowCallParenthesized?: boolean;
}
interface PipelineOperatorPluginOptions {
proposal: BABEL_8_BREAKING extends false
? "minimal" | "fsharp" | "hack" | "smart"
: "fsharp" | "hack";
topicToken?: "%" | "#" | "@@" | "^^" | "^";
}
interface RecordAndTuplePluginOptions {
syntaxType: "bar" | "hash";
}
type FlowPluginOptions = BABEL_8_BREAKING extends true
? {
all?: boolean;
enums?: boolean;
}
: {
all?: boolean;
};
interface TypeScriptPluginOptions {
dts?: boolean;
disallowAmbiguousJSXLike?: boolean;
}
type Plugin = PluginConfig;
type SourceType = "script" | "commonjs" | "module" | "unambiguous";
interface Options {
/**
* By default, import and export declarations can only appear at a program's top level.
* Setting this option to true allows them anywhere where a statement is allowed.
*/
allowImportExportEverywhere?: boolean;
/**
* By default, await use is not allowed outside of an async function.
* Set this to true to accept such code.
*/
allowAwaitOutsideFunction?: boolean;
/**
* By default, a return statement at the top level raises an error.
* Set this to true to accept such code.
*/
allowReturnOutsideFunction?: boolean;
/**
* By default, new.target use is not allowed outside of a function or class.
* Set this to true to accept such code.
*/
allowNewTargetOutsideFunction?: boolean;
/**
* By default, super calls are not allowed outside of a method.
* Set this to true to accept such code.
*/
allowSuperOutsideMethod?: boolean;
/**
* By default, exported identifiers must refer to a declared variable.
* Set this to true to allow export statements to reference undeclared variables.
*/
allowUndeclaredExports?: boolean;
/**
* By default, yield use is not allowed outside of a generator function.
* Set this to true to accept such code.
*/
allowYieldOutsideFunction?: boolean;
/**
* By default, Babel parser JavaScript code according to Annex B syntax.
* Set this to `false` to disable such behavior.
*/
annexB?: boolean;
/**
* By default, Babel attaches comments to adjacent AST nodes.
* When this option is set to false, comments are not attached.
* It can provide up to 30% performance improvement when the input code has many comments.
* @babel/eslint-parser will set it for you.
* It is not recommended to use attachComment: false with Babel transform,
* as doing so removes all the comments in output code, and renders annotations such as
* /* istanbul ignore next *\/ nonfunctional.
*/
attachComment?: boolean;
/**
* By default, Babel always throws an error when it finds some invalid code.
* When this option is set to true, it will store the parsing error and
* try to continue parsing the invalid input file.
*/
errorRecovery?: boolean;
/**
* Indicate the mode the code should be parsed in.
* Can be one of "script", "commonjs", "module", or "unambiguous". Defaults to "script".
* "unambiguous" will make @babel/parser attempt to guess, based on the presence
* of ES6 import or export statements.
* Files with ES6 imports and exports are considered "module" and are otherwise "script".
*
* Use "commonjs" to parse code that is intended to be run in a CommonJS environment such as Node.js.
*/
sourceType?: SourceType;
/**
* Correlate output AST nodes with their source filename.
* Useful when generating code and source maps from the ASTs of multiple input files.
*/
sourceFilename?: string;
/**
* By default, all source indexes start from 0.
* You can provide a start index to alternatively start with.
* Useful for integration with other source tools.
*/
startIndex?: number;
/**
* By default, the first line of code parsed is treated as line 1.
* You can provide a line number to alternatively start with.
* Useful for integration with other source tools.
*/
startLine?: number;
/**
* By default, the parsed code is treated as if it starts from line 1, column 0.
* You can provide a column number to alternatively start with.
* Useful for integration with other source tools.
*/
startColumn?: number;
/**
* Array containing the plugins that you want to enable.
*/
plugins?: Plugin[];
/**
* Should the parser work in strict mode.
* Defaults to true if sourceType === 'module'. Otherwise, false.
*/
strictMode?: boolean;
/**
* Adds a ranges property to each node: [node.start, node.end]
*/
ranges?: boolean;
/**
* Adds all parsed tokens to a tokens property on the File node.
*/
tokens?: boolean;
/**
* By default, the parser adds information about parentheses by setting
* `extra.parenthesized` to `true` as needed.
* When this option is `true` the parser creates `ParenthesizedExpression`
* AST nodes instead of using the `extra` property.
*/
createParenthesizedExpressions?: boolean;
/**
* The default is false in Babel 7 and true in Babel 8
* Set this to true to parse it as an `ImportExpression` node.
* Otherwise `import(foo)` is parsed as `CallExpression(Import, [Identifier(foo)])`.
*/
createImportExpressions?: boolean;
}
type ParserOptions = Partial<Options>;
type ParseError = ParseError$1<object>;
type ParseResult<Result extends File | Expression = File> = Result & {
comments: File["comments"];
errors: null | ParseError[];
tokens?: File["tokens"];
};
/**
* Parse the provided code as an entire ECMAScript program.
*/
declare function parse(input: string, options?: ParserOptions): ParseResult<File>;
declare function parseExpression(input: string, options?: ParserOptions): ParseResult<Expression>;
declare const tokTypes: {
// todo(flow->ts) real token type
[name: string]: any;
};
export { DecoratorsPluginOptions, FlowPluginOptions, ParseError, ParseResult, ParserOptions, PluginConfig as ParserPlugin, ParserPluginWithOptions, PipelineOperatorPluginOptions, RecordAndTuplePluginOptions, TypeScriptPluginOptions, parse, parseExpression, tokTypes };
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# @babel/types
> Babel Types is a Lodash-esque utility library for AST nodes
See our website [@babel/types](https://babeljs.io/docs/babel-types) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20types%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/types
```
or using yarn:
```sh
yarn add @babel/types --dev
```
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = assertNode;
var _isNode = require("../validators/isNode.js");
function assertNode(node) {
if (!(0, _isNode.default)(node)) {
var _node$type;
const type = (_node$type = node == null ? void 0 : node.type) != null ? _node$type : JSON.stringify(node);
throw new TypeError(`Not a valid node of type "${type}"`);
}
}
//# sourceMappingURL=assertNode.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_isNode","require","assertNode","node","isNode","_node$type","type","JSON","stringify","TypeError"],"sources":["../../src/asserts/assertNode.ts"],"sourcesContent":["import isNode from \"../validators/isNode.ts\";\nimport type * as t from \"../index.ts\";\n\nexport default function assertNode(node?: any): asserts node is t.Node {\n if (!isNode(node)) {\n const type = node?.type ?? JSON.stringify(node);\n throw new TypeError(`Not a valid node of type \"${type}\"`);\n }\n}\n"],"mappings":";;;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AAGe,SAASC,UAAUA,CAACC,IAAU,EAA0B;EACrE,IAAI,CAAC,IAAAC,eAAM,EAACD,IAAI,CAAC,EAAE;IAAA,IAAAE,UAAA;IACjB,MAAMC,IAAI,IAAAD,UAAA,GAAGF,IAAI,oBAAJA,IAAI,CAAEG,IAAI,YAAAD,UAAA,GAAIE,IAAI,CAACC,SAAS,CAACL,IAAI,CAAC;IAC/C,MAAM,IAAIM,SAAS,CAAC,6BAA6BH,IAAI,GAAG,CAAC;EAC3D;AACF","ignoreList":[]}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
"use strict";
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = createFlowUnionType;
var _index = require("../generated/index.js");
var _removeTypeDuplicates = require("../../modifications/flow/removeTypeDuplicates.js");
function createFlowUnionType(types) {
const flattened = (0, _removeTypeDuplicates.default)(types);
if (flattened.length === 1) {
return flattened[0];
} else {
return (0, _index.unionTypeAnnotation)(flattened);
}
}
//# sourceMappingURL=createFlowUnionType.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_index","require","_removeTypeDuplicates","createFlowUnionType","types","flattened","removeTypeDuplicates","length","unionTypeAnnotation"],"sources":["../../../src/builders/flow/createFlowUnionType.ts"],"sourcesContent":["import { unionTypeAnnotation } from \"../generated/index.ts\";\nimport removeTypeDuplicates from \"../../modifications/flow/removeTypeDuplicates.ts\";\nimport type * as t from \"../../index.ts\";\n\n/**\n * Takes an array of `types` and flattens them, removing duplicates and\n * returns a `UnionTypeAnnotation` node containing them.\n */\nexport default function createFlowUnionType<T extends t.FlowType>(\n types: [T] | T[],\n): T | t.UnionTypeAnnotation {\n const flattened = removeTypeDuplicates(types);\n\n if (flattened.length === 1) {\n return flattened[0] as T;\n } else {\n return unionTypeAnnotation(flattened);\n }\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,qBAAA,GAAAD,OAAA;AAOe,SAASE,mBAAmBA,CACzCC,KAAgB,EACW;EAC3B,MAAMC,SAAS,GAAG,IAAAC,6BAAoB,EAACF,KAAK,CAAC;EAE7C,IAAIC,SAAS,CAACE,MAAM,KAAK,CAAC,EAAE;IAC1B,OAAOF,SAAS,CAAC,CAAC,CAAC;EACrB,CAAC,MAAM;IACL,OAAO,IAAAG,0BAAmB,EAACH,SAAS,CAAC;EACvC;AACF","ignoreList":[]}
@@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _index = require("../generated/index.js");
var _default = exports.default = createTypeAnnotationBasedOnTypeof;
function createTypeAnnotationBasedOnTypeof(type) {
switch (type) {
case "string":
return (0, _index.stringTypeAnnotation)();
case "number":
return (0, _index.numberTypeAnnotation)();
case "undefined":
return (0, _index.voidTypeAnnotation)();
case "boolean":
return (0, _index.booleanTypeAnnotation)();
case "function":
return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Function"));
case "object":
return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Object"));
case "symbol":
return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Symbol"));
case "bigint":
return (0, _index.anyTypeAnnotation)();
}
throw new Error("Invalid typeof value: " + type);
}
//# sourceMappingURL=createTypeAnnotationBasedOnTypeof.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_index","require","_default","exports","default","createTypeAnnotationBasedOnTypeof","type","stringTypeAnnotation","numberTypeAnnotation","voidTypeAnnotation","booleanTypeAnnotation","genericTypeAnnotation","identifier","anyTypeAnnotation","Error"],"sources":["../../../src/builders/flow/createTypeAnnotationBasedOnTypeof.ts"],"sourcesContent":["import {\n anyTypeAnnotation,\n stringTypeAnnotation,\n numberTypeAnnotation,\n voidTypeAnnotation,\n booleanTypeAnnotation,\n genericTypeAnnotation,\n identifier,\n} from \"../generated/index.ts\";\nimport type * as t from \"../../index.ts\";\n\nexport default createTypeAnnotationBasedOnTypeof as {\n (type: \"string\"): t.StringTypeAnnotation;\n (type: \"number\"): t.NumberTypeAnnotation;\n (type: \"undefined\"): t.VoidTypeAnnotation;\n (type: \"boolean\"): t.BooleanTypeAnnotation;\n (type: \"function\"): t.GenericTypeAnnotation;\n (type: \"object\"): t.GenericTypeAnnotation;\n (type: \"symbol\"): t.GenericTypeAnnotation;\n (type: \"bigint\"): t.AnyTypeAnnotation;\n};\n\n/**\n * Create a type annotation based on typeof expression.\n */\nfunction createTypeAnnotationBasedOnTypeof(type: string): t.FlowType {\n switch (type) {\n case \"string\":\n return stringTypeAnnotation();\n case \"number\":\n return numberTypeAnnotation();\n case \"undefined\":\n return voidTypeAnnotation();\n case \"boolean\":\n return booleanTypeAnnotation();\n case \"function\":\n return genericTypeAnnotation(identifier(\"Function\"));\n case \"object\":\n return genericTypeAnnotation(identifier(\"Object\"));\n case \"symbol\":\n return genericTypeAnnotation(identifier(\"Symbol\"));\n case \"bigint\":\n // todo: use BigInt annotation when Flow supports BigInt\n // https://github.com/facebook/flow/issues/6639\n return anyTypeAnnotation();\n }\n throw new Error(\"Invalid typeof value: \" + type);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAQ+B,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAGhBC,iCAAiC;AAchD,SAASA,iCAAiCA,CAACC,IAAY,EAAc;EACnE,QAAQA,IAAI;IACV,KAAK,QAAQ;MACX,OAAO,IAAAC,2BAAoB,EAAC,CAAC;IAC/B,KAAK,QAAQ;MACX,OAAO,IAAAC,2BAAoB,EAAC,CAAC;IAC/B,KAAK,WAAW;MACd,OAAO,IAAAC,yBAAkB,EAAC,CAAC;IAC7B,KAAK,SAAS;MACZ,OAAO,IAAAC,4BAAqB,EAAC,CAAC;IAChC,KAAK,UAAU;MACb,OAAO,IAAAC,4BAAqB,EAAC,IAAAC,iBAAU,EAAC,UAAU,CAAC,CAAC;IACtD,KAAK,QAAQ;MACX,OAAO,IAAAD,4BAAqB,EAAC,IAAAC,iBAAU,EAAC,QAAQ,CAAC,CAAC;IACpD,KAAK,QAAQ;MACX,OAAO,IAAAD,4BAAqB,EAAC,IAAAC,iBAAU,EAAC,QAAQ,CAAC,CAAC;IACpD,KAAK,QAAQ;MAGX,OAAO,IAAAC,wBAAiB,EAAC,CAAC;EAC9B;EACA,MAAM,IAAIC,KAAK,CAAC,wBAAwB,GAAGR,IAAI,CAAC;AAClD","ignoreList":[]}
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _lowercase = require("./lowercase.js");
Object.keys(_lowercase).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _lowercase[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _lowercase[key];
}
});
});
var _uppercase = require("./uppercase.js");
Object.keys(_uppercase).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _uppercase[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _uppercase[key];
}
});
});
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,272 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JSXIdentifier = exports.JSXFragment = exports.JSXExpressionContainer = exports.JSXEmptyExpression = exports.JSXElement = exports.JSXClosingFragment = exports.JSXClosingElement = exports.JSXAttribute = exports.IntersectionTypeAnnotation = exports.InterpreterDirective = exports.InterfaceTypeAnnotation = exports.InterfaceExtends = exports.InterfaceDeclaration = exports.InferredPredicate = exports.IndexedAccessType = exports.ImportSpecifier = exports.ImportNamespaceSpecifier = exports.ImportExpression = exports.ImportDefaultSpecifier = exports.ImportDeclaration = exports.ImportAttribute = exports.Import = exports.IfStatement = exports.Identifier = exports.GenericTypeAnnotation = exports.FunctionTypeParam = exports.FunctionTypeAnnotation = exports.FunctionExpression = exports.FunctionDeclaration = exports.ForStatement = exports.ForOfStatement = exports.ForInStatement = exports.File = exports.ExpressionStatement = exports.ExportSpecifier = exports.ExportNamespaceSpecifier = exports.ExportNamedDeclaration = exports.ExportDefaultSpecifier = exports.ExportDefaultDeclaration = exports.ExportAllDeclaration = exports.ExistsTypeAnnotation = exports.EnumSymbolBody = exports.EnumStringMember = exports.EnumStringBody = exports.EnumNumberMember = exports.EnumNumberBody = exports.EnumDefaultedMember = exports.EnumDeclaration = exports.EnumBooleanMember = exports.EnumBooleanBody = exports.EmptyTypeAnnotation = exports.EmptyStatement = exports.DoWhileStatement = exports.DoExpression = exports.DirectiveLiteral = exports.Directive = exports.Decorator = exports.DeclaredPredicate = exports.DeclareVariable = exports.DeclareTypeAlias = exports.DeclareOpaqueType = exports.DeclareModuleExports = exports.DeclareModule = exports.DeclareInterface = exports.DeclareFunction = exports.DeclareExportDeclaration = exports.DeclareExportAllDeclaration = exports.DeclareClass = exports.DecimalLiteral = exports.DebuggerStatement = exports.ContinueStatement = exports.ConditionalExpression = exports.ClassProperty = exports.ClassPrivateProperty = exports.ClassPrivateMethod = exports.ClassMethod = exports.ClassImplements = exports.ClassExpression = exports.ClassDeclaration = exports.ClassBody = exports.ClassAccessorProperty = exports.CatchClause = exports.CallExpression = exports.BreakStatement = exports.BooleanTypeAnnotation = exports.BooleanLiteralTypeAnnotation = exports.BooleanLiteral = exports.BlockStatement = exports.BindExpression = exports.BinaryExpression = exports.BigIntLiteral = exports.AwaitExpression = exports.AssignmentPattern = exports.AssignmentExpression = exports.ArrowFunctionExpression = exports.ArrayTypeAnnotation = exports.ArrayPattern = exports.ArrayExpression = exports.ArgumentPlaceholder = exports.AnyTypeAnnotation = void 0;
exports.TSNumberKeyword = exports.TSNullKeyword = exports.TSNonNullExpression = exports.TSNeverKeyword = exports.TSNamespaceExportDeclaration = exports.TSNamedTupleMember = exports.TSModuleDeclaration = exports.TSModuleBlock = exports.TSMethodSignature = exports.TSMappedType = exports.TSLiteralType = exports.TSIntrinsicKeyword = exports.TSIntersectionType = exports.TSInterfaceDeclaration = exports.TSInterfaceBody = exports.TSInstantiationExpression = exports.TSInferType = exports.TSIndexedAccessType = exports.TSIndexSignature = exports.TSImportType = exports.TSImportEqualsDeclaration = exports.TSFunctionType = exports.TSExternalModuleReference = exports.TSExpressionWithTypeArguments = exports.TSExportAssignment = exports.TSEnumMember = exports.TSEnumDeclaration = exports.TSEnumBody = exports.TSDeclareMethod = exports.TSDeclareFunction = exports.TSConstructorType = exports.TSConstructSignatureDeclaration = exports.TSConditionalType = exports.TSCallSignatureDeclaration = exports.TSBooleanKeyword = exports.TSBigIntKeyword = exports.TSAsExpression = exports.TSArrayType = exports.TSAnyKeyword = exports.SymbolTypeAnnotation = exports.SwitchStatement = exports.SwitchCase = exports.Super = exports.StringTypeAnnotation = exports.StringLiteralTypeAnnotation = exports.StringLiteral = exports.StaticBlock = exports.SpreadProperty = exports.SpreadElement = exports.SequenceExpression = exports.ReturnStatement = exports.RestProperty = exports.RestElement = exports.RegexLiteral = exports.RegExpLiteral = exports.RecordExpression = exports.QualifiedTypeIdentifier = exports.Program = exports.PrivateName = exports.Placeholder = exports.PipelineTopicExpression = exports.PipelinePrimaryTopicReference = exports.PipelineBareFunction = exports.ParenthesizedExpression = exports.OptionalMemberExpression = exports.OptionalIndexedAccessType = exports.OptionalCallExpression = exports.OpaqueType = exports.ObjectTypeSpreadProperty = exports.ObjectTypeProperty = exports.ObjectTypeInternalSlot = exports.ObjectTypeIndexer = exports.ObjectTypeCallProperty = exports.ObjectTypeAnnotation = exports.ObjectProperty = exports.ObjectPattern = exports.ObjectMethod = exports.ObjectExpression = exports.NumericLiteral = exports.NumberTypeAnnotation = exports.NumberLiteralTypeAnnotation = exports.NumberLiteral = exports.NullableTypeAnnotation = exports.NullLiteralTypeAnnotation = exports.NullLiteral = exports.Noop = exports.NewExpression = exports.ModuleExpression = exports.MixedTypeAnnotation = exports.MetaProperty = exports.MemberExpression = exports.LogicalExpression = exports.LabeledStatement = exports.JSXText = exports.JSXSpreadChild = exports.JSXSpreadAttribute = exports.JSXOpeningFragment = exports.JSXOpeningElement = exports.JSXNamespacedName = exports.JSXMemberExpression = void 0;
exports.YieldExpression = exports.WithStatement = exports.WhileStatement = exports.VoidTypeAnnotation = exports.VoidPattern = exports.Variance = exports.VariableDeclarator = exports.VariableDeclaration = exports.V8IntrinsicIdentifier = exports.UpdateExpression = exports.UnionTypeAnnotation = exports.UnaryExpression = exports.TypeofTypeAnnotation = exports.TypeParameterInstantiation = exports.TypeParameterDeclaration = exports.TypeParameter = exports.TypeCastExpression = exports.TypeAnnotation = exports.TypeAlias = exports.TupleTypeAnnotation = exports.TupleExpression = exports.TryStatement = exports.TopicReference = exports.ThrowStatement = exports.ThisTypeAnnotation = exports.ThisExpression = exports.TemplateLiteral = exports.TemplateElement = exports.TaggedTemplateExpression = exports.TSVoidKeyword = exports.TSUnknownKeyword = exports.TSUnionType = exports.TSUndefinedKeyword = exports.TSTypeReference = exports.TSTypeQuery = exports.TSTypePredicate = exports.TSTypeParameterInstantiation = exports.TSTypeParameterDeclaration = exports.TSTypeParameter = exports.TSTypeOperator = exports.TSTypeLiteral = exports.TSTypeAssertion = exports.TSTypeAnnotation = exports.TSTypeAliasDeclaration = exports.TSTupleType = exports.TSThisType = exports.TSTemplateLiteralType = exports.TSSymbolKeyword = exports.TSStringKeyword = exports.TSSatisfiesExpression = exports.TSRestType = exports.TSQualifiedName = exports.TSPropertySignature = exports.TSParenthesizedType = exports.TSParameterProperty = exports.TSOptionalType = exports.TSObjectKeyword = void 0;
var b = require("./lowercase.js");
var _deprecationWarning = require("../../utils/deprecationWarning.js");
function alias(lowercase) {
return b[lowercase];
}
const ArrayExpression = exports.ArrayExpression = alias("arrayExpression"),
AssignmentExpression = exports.AssignmentExpression = alias("assignmentExpression"),
BinaryExpression = exports.BinaryExpression = alias("binaryExpression"),
InterpreterDirective = exports.InterpreterDirective = alias("interpreterDirective"),
Directive = exports.Directive = alias("directive"),
DirectiveLiteral = exports.DirectiveLiteral = alias("directiveLiteral"),
BlockStatement = exports.BlockStatement = alias("blockStatement"),
BreakStatement = exports.BreakStatement = alias("breakStatement"),
CallExpression = exports.CallExpression = alias("callExpression"),
CatchClause = exports.CatchClause = alias("catchClause"),
ConditionalExpression = exports.ConditionalExpression = alias("conditionalExpression"),
ContinueStatement = exports.ContinueStatement = alias("continueStatement"),
DebuggerStatement = exports.DebuggerStatement = alias("debuggerStatement"),
DoWhileStatement = exports.DoWhileStatement = alias("doWhileStatement"),
EmptyStatement = exports.EmptyStatement = alias("emptyStatement"),
ExpressionStatement = exports.ExpressionStatement = alias("expressionStatement"),
File = exports.File = alias("file"),
ForInStatement = exports.ForInStatement = alias("forInStatement"),
ForStatement = exports.ForStatement = alias("forStatement"),
FunctionDeclaration = exports.FunctionDeclaration = alias("functionDeclaration"),
FunctionExpression = exports.FunctionExpression = alias("functionExpression"),
Identifier = exports.Identifier = alias("identifier"),
IfStatement = exports.IfStatement = alias("ifStatement"),
LabeledStatement = exports.LabeledStatement = alias("labeledStatement"),
StringLiteral = exports.StringLiteral = alias("stringLiteral"),
NumericLiteral = exports.NumericLiteral = alias("numericLiteral"),
NullLiteral = exports.NullLiteral = alias("nullLiteral"),
BooleanLiteral = exports.BooleanLiteral = alias("booleanLiteral"),
RegExpLiteral = exports.RegExpLiteral = alias("regExpLiteral"),
LogicalExpression = exports.LogicalExpression = alias("logicalExpression"),
MemberExpression = exports.MemberExpression = alias("memberExpression"),
NewExpression = exports.NewExpression = alias("newExpression"),
Program = exports.Program = alias("program"),
ObjectExpression = exports.ObjectExpression = alias("objectExpression"),
ObjectMethod = exports.ObjectMethod = alias("objectMethod"),
ObjectProperty = exports.ObjectProperty = alias("objectProperty"),
RestElement = exports.RestElement = alias("restElement"),
ReturnStatement = exports.ReturnStatement = alias("returnStatement"),
SequenceExpression = exports.SequenceExpression = alias("sequenceExpression"),
ParenthesizedExpression = exports.ParenthesizedExpression = alias("parenthesizedExpression"),
SwitchCase = exports.SwitchCase = alias("switchCase"),
SwitchStatement = exports.SwitchStatement = alias("switchStatement"),
ThisExpression = exports.ThisExpression = alias("thisExpression"),
ThrowStatement = exports.ThrowStatement = alias("throwStatement"),
TryStatement = exports.TryStatement = alias("tryStatement"),
UnaryExpression = exports.UnaryExpression = alias("unaryExpression"),
UpdateExpression = exports.UpdateExpression = alias("updateExpression"),
VariableDeclaration = exports.VariableDeclaration = alias("variableDeclaration"),
VariableDeclarator = exports.VariableDeclarator = alias("variableDeclarator"),
WhileStatement = exports.WhileStatement = alias("whileStatement"),
WithStatement = exports.WithStatement = alias("withStatement"),
AssignmentPattern = exports.AssignmentPattern = alias("assignmentPattern"),
ArrayPattern = exports.ArrayPattern = alias("arrayPattern"),
ArrowFunctionExpression = exports.ArrowFunctionExpression = alias("arrowFunctionExpression"),
ClassBody = exports.ClassBody = alias("classBody"),
ClassExpression = exports.ClassExpression = alias("classExpression"),
ClassDeclaration = exports.ClassDeclaration = alias("classDeclaration"),
ExportAllDeclaration = exports.ExportAllDeclaration = alias("exportAllDeclaration"),
ExportDefaultDeclaration = exports.ExportDefaultDeclaration = alias("exportDefaultDeclaration"),
ExportNamedDeclaration = exports.ExportNamedDeclaration = alias("exportNamedDeclaration"),
ExportSpecifier = exports.ExportSpecifier = alias("exportSpecifier"),
ForOfStatement = exports.ForOfStatement = alias("forOfStatement"),
ImportDeclaration = exports.ImportDeclaration = alias("importDeclaration"),
ImportDefaultSpecifier = exports.ImportDefaultSpecifier = alias("importDefaultSpecifier"),
ImportNamespaceSpecifier = exports.ImportNamespaceSpecifier = alias("importNamespaceSpecifier"),
ImportSpecifier = exports.ImportSpecifier = alias("importSpecifier"),
ImportExpression = exports.ImportExpression = alias("importExpression"),
MetaProperty = exports.MetaProperty = alias("metaProperty"),
ClassMethod = exports.ClassMethod = alias("classMethod"),
ObjectPattern = exports.ObjectPattern = alias("objectPattern"),
SpreadElement = exports.SpreadElement = alias("spreadElement"),
Super = exports.Super = alias("super"),
TaggedTemplateExpression = exports.TaggedTemplateExpression = alias("taggedTemplateExpression"),
TemplateElement = exports.TemplateElement = alias("templateElement"),
TemplateLiteral = exports.TemplateLiteral = alias("templateLiteral"),
YieldExpression = exports.YieldExpression = alias("yieldExpression"),
AwaitExpression = exports.AwaitExpression = alias("awaitExpression"),
Import = exports.Import = alias("import"),
BigIntLiteral = exports.BigIntLiteral = alias("bigIntLiteral"),
ExportNamespaceSpecifier = exports.ExportNamespaceSpecifier = alias("exportNamespaceSpecifier"),
OptionalMemberExpression = exports.OptionalMemberExpression = alias("optionalMemberExpression"),
OptionalCallExpression = exports.OptionalCallExpression = alias("optionalCallExpression"),
ClassProperty = exports.ClassProperty = alias("classProperty"),
ClassAccessorProperty = exports.ClassAccessorProperty = alias("classAccessorProperty"),
ClassPrivateProperty = exports.ClassPrivateProperty = alias("classPrivateProperty"),
ClassPrivateMethod = exports.ClassPrivateMethod = alias("classPrivateMethod"),
PrivateName = exports.PrivateName = alias("privateName"),
StaticBlock = exports.StaticBlock = alias("staticBlock"),
ImportAttribute = exports.ImportAttribute = alias("importAttribute"),
AnyTypeAnnotation = exports.AnyTypeAnnotation = alias("anyTypeAnnotation"),
ArrayTypeAnnotation = exports.ArrayTypeAnnotation = alias("arrayTypeAnnotation"),
BooleanTypeAnnotation = exports.BooleanTypeAnnotation = alias("booleanTypeAnnotation"),
BooleanLiteralTypeAnnotation = exports.BooleanLiteralTypeAnnotation = alias("booleanLiteralTypeAnnotation"),
NullLiteralTypeAnnotation = exports.NullLiteralTypeAnnotation = alias("nullLiteralTypeAnnotation"),
ClassImplements = exports.ClassImplements = alias("classImplements"),
DeclareClass = exports.DeclareClass = alias("declareClass"),
DeclareFunction = exports.DeclareFunction = alias("declareFunction"),
DeclareInterface = exports.DeclareInterface = alias("declareInterface"),
DeclareModule = exports.DeclareModule = alias("declareModule"),
DeclareModuleExports = exports.DeclareModuleExports = alias("declareModuleExports"),
DeclareTypeAlias = exports.DeclareTypeAlias = alias("declareTypeAlias"),
DeclareOpaqueType = exports.DeclareOpaqueType = alias("declareOpaqueType"),
DeclareVariable = exports.DeclareVariable = alias("declareVariable"),
DeclareExportDeclaration = exports.DeclareExportDeclaration = alias("declareExportDeclaration"),
DeclareExportAllDeclaration = exports.DeclareExportAllDeclaration = alias("declareExportAllDeclaration"),
DeclaredPredicate = exports.DeclaredPredicate = alias("declaredPredicate"),
ExistsTypeAnnotation = exports.ExistsTypeAnnotation = alias("existsTypeAnnotation"),
FunctionTypeAnnotation = exports.FunctionTypeAnnotation = alias("functionTypeAnnotation"),
FunctionTypeParam = exports.FunctionTypeParam = alias("functionTypeParam"),
GenericTypeAnnotation = exports.GenericTypeAnnotation = alias("genericTypeAnnotation"),
InferredPredicate = exports.InferredPredicate = alias("inferredPredicate"),
InterfaceExtends = exports.InterfaceExtends = alias("interfaceExtends"),
InterfaceDeclaration = exports.InterfaceDeclaration = alias("interfaceDeclaration"),
InterfaceTypeAnnotation = exports.InterfaceTypeAnnotation = alias("interfaceTypeAnnotation"),
IntersectionTypeAnnotation = exports.IntersectionTypeAnnotation = alias("intersectionTypeAnnotation"),
MixedTypeAnnotation = exports.MixedTypeAnnotation = alias("mixedTypeAnnotation"),
EmptyTypeAnnotation = exports.EmptyTypeAnnotation = alias("emptyTypeAnnotation"),
NullableTypeAnnotation = exports.NullableTypeAnnotation = alias("nullableTypeAnnotation"),
NumberLiteralTypeAnnotation = exports.NumberLiteralTypeAnnotation = alias("numberLiteralTypeAnnotation"),
NumberTypeAnnotation = exports.NumberTypeAnnotation = alias("numberTypeAnnotation"),
ObjectTypeAnnotation = exports.ObjectTypeAnnotation = alias("objectTypeAnnotation"),
ObjectTypeInternalSlot = exports.ObjectTypeInternalSlot = alias("objectTypeInternalSlot"),
ObjectTypeCallProperty = exports.ObjectTypeCallProperty = alias("objectTypeCallProperty"),
ObjectTypeIndexer = exports.ObjectTypeIndexer = alias("objectTypeIndexer"),
ObjectTypeProperty = exports.ObjectTypeProperty = alias("objectTypeProperty"),
ObjectTypeSpreadProperty = exports.ObjectTypeSpreadProperty = alias("objectTypeSpreadProperty"),
OpaqueType = exports.OpaqueType = alias("opaqueType"),
QualifiedTypeIdentifier = exports.QualifiedTypeIdentifier = alias("qualifiedTypeIdentifier"),
StringLiteralTypeAnnotation = exports.StringLiteralTypeAnnotation = alias("stringLiteralTypeAnnotation"),
StringTypeAnnotation = exports.StringTypeAnnotation = alias("stringTypeAnnotation"),
SymbolTypeAnnotation = exports.SymbolTypeAnnotation = alias("symbolTypeAnnotation"),
ThisTypeAnnotation = exports.ThisTypeAnnotation = alias("thisTypeAnnotation"),
TupleTypeAnnotation = exports.TupleTypeAnnotation = alias("tupleTypeAnnotation"),
TypeofTypeAnnotation = exports.TypeofTypeAnnotation = alias("typeofTypeAnnotation"),
TypeAlias = exports.TypeAlias = alias("typeAlias"),
TypeAnnotation = exports.TypeAnnotation = alias("typeAnnotation"),
TypeCastExpression = exports.TypeCastExpression = alias("typeCastExpression"),
TypeParameter = exports.TypeParameter = alias("typeParameter"),
TypeParameterDeclaration = exports.TypeParameterDeclaration = alias("typeParameterDeclaration"),
TypeParameterInstantiation = exports.TypeParameterInstantiation = alias("typeParameterInstantiation"),
UnionTypeAnnotation = exports.UnionTypeAnnotation = alias("unionTypeAnnotation"),
Variance = exports.Variance = alias("variance"),
VoidTypeAnnotation = exports.VoidTypeAnnotation = alias("voidTypeAnnotation"),
EnumDeclaration = exports.EnumDeclaration = alias("enumDeclaration"),
EnumBooleanBody = exports.EnumBooleanBody = alias("enumBooleanBody"),
EnumNumberBody = exports.EnumNumberBody = alias("enumNumberBody"),
EnumStringBody = exports.EnumStringBody = alias("enumStringBody"),
EnumSymbolBody = exports.EnumSymbolBody = alias("enumSymbolBody"),
EnumBooleanMember = exports.EnumBooleanMember = alias("enumBooleanMember"),
EnumNumberMember = exports.EnumNumberMember = alias("enumNumberMember"),
EnumStringMember = exports.EnumStringMember = alias("enumStringMember"),
EnumDefaultedMember = exports.EnumDefaultedMember = alias("enumDefaultedMember"),
IndexedAccessType = exports.IndexedAccessType = alias("indexedAccessType"),
OptionalIndexedAccessType = exports.OptionalIndexedAccessType = alias("optionalIndexedAccessType"),
JSXAttribute = exports.JSXAttribute = alias("jsxAttribute"),
JSXClosingElement = exports.JSXClosingElement = alias("jsxClosingElement"),
JSXElement = exports.JSXElement = alias("jsxElement"),
JSXEmptyExpression = exports.JSXEmptyExpression = alias("jsxEmptyExpression"),
JSXExpressionContainer = exports.JSXExpressionContainer = alias("jsxExpressionContainer"),
JSXSpreadChild = exports.JSXSpreadChild = alias("jsxSpreadChild"),
JSXIdentifier = exports.JSXIdentifier = alias("jsxIdentifier"),
JSXMemberExpression = exports.JSXMemberExpression = alias("jsxMemberExpression"),
JSXNamespacedName = exports.JSXNamespacedName = alias("jsxNamespacedName"),
JSXOpeningElement = exports.JSXOpeningElement = alias("jsxOpeningElement"),
JSXSpreadAttribute = exports.JSXSpreadAttribute = alias("jsxSpreadAttribute"),
JSXText = exports.JSXText = alias("jsxText"),
JSXFragment = exports.JSXFragment = alias("jsxFragment"),
JSXOpeningFragment = exports.JSXOpeningFragment = alias("jsxOpeningFragment"),
JSXClosingFragment = exports.JSXClosingFragment = alias("jsxClosingFragment"),
Noop = exports.Noop = alias("noop"),
Placeholder = exports.Placeholder = alias("placeholder"),
V8IntrinsicIdentifier = exports.V8IntrinsicIdentifier = alias("v8IntrinsicIdentifier"),
ArgumentPlaceholder = exports.ArgumentPlaceholder = alias("argumentPlaceholder"),
BindExpression = exports.BindExpression = alias("bindExpression"),
Decorator = exports.Decorator = alias("decorator"),
DoExpression = exports.DoExpression = alias("doExpression"),
ExportDefaultSpecifier = exports.ExportDefaultSpecifier = alias("exportDefaultSpecifier"),
RecordExpression = exports.RecordExpression = alias("recordExpression"),
TupleExpression = exports.TupleExpression = alias("tupleExpression"),
DecimalLiteral = exports.DecimalLiteral = alias("decimalLiteral"),
ModuleExpression = exports.ModuleExpression = alias("moduleExpression"),
TopicReference = exports.TopicReference = alias("topicReference"),
PipelineTopicExpression = exports.PipelineTopicExpression = alias("pipelineTopicExpression"),
PipelineBareFunction = exports.PipelineBareFunction = alias("pipelineBareFunction"),
PipelinePrimaryTopicReference = exports.PipelinePrimaryTopicReference = alias("pipelinePrimaryTopicReference"),
VoidPattern = exports.VoidPattern = alias("voidPattern"),
TSParameterProperty = exports.TSParameterProperty = alias("tsParameterProperty"),
TSDeclareFunction = exports.TSDeclareFunction = alias("tsDeclareFunction"),
TSDeclareMethod = exports.TSDeclareMethod = alias("tsDeclareMethod"),
TSQualifiedName = exports.TSQualifiedName = alias("tsQualifiedName"),
TSCallSignatureDeclaration = exports.TSCallSignatureDeclaration = alias("tsCallSignatureDeclaration"),
TSConstructSignatureDeclaration = exports.TSConstructSignatureDeclaration = alias("tsConstructSignatureDeclaration"),
TSPropertySignature = exports.TSPropertySignature = alias("tsPropertySignature"),
TSMethodSignature = exports.TSMethodSignature = alias("tsMethodSignature"),
TSIndexSignature = exports.TSIndexSignature = alias("tsIndexSignature"),
TSAnyKeyword = exports.TSAnyKeyword = alias("tsAnyKeyword"),
TSBooleanKeyword = exports.TSBooleanKeyword = alias("tsBooleanKeyword"),
TSBigIntKeyword = exports.TSBigIntKeyword = alias("tsBigIntKeyword"),
TSIntrinsicKeyword = exports.TSIntrinsicKeyword = alias("tsIntrinsicKeyword"),
TSNeverKeyword = exports.TSNeverKeyword = alias("tsNeverKeyword"),
TSNullKeyword = exports.TSNullKeyword = alias("tsNullKeyword"),
TSNumberKeyword = exports.TSNumberKeyword = alias("tsNumberKeyword"),
TSObjectKeyword = exports.TSObjectKeyword = alias("tsObjectKeyword"),
TSStringKeyword = exports.TSStringKeyword = alias("tsStringKeyword"),
TSSymbolKeyword = exports.TSSymbolKeyword = alias("tsSymbolKeyword"),
TSUndefinedKeyword = exports.TSUndefinedKeyword = alias("tsUndefinedKeyword"),
TSUnknownKeyword = exports.TSUnknownKeyword = alias("tsUnknownKeyword"),
TSVoidKeyword = exports.TSVoidKeyword = alias("tsVoidKeyword"),
TSThisType = exports.TSThisType = alias("tsThisType"),
TSFunctionType = exports.TSFunctionType = alias("tsFunctionType"),
TSConstructorType = exports.TSConstructorType = alias("tsConstructorType"),
TSTypeReference = exports.TSTypeReference = alias("tsTypeReference"),
TSTypePredicate = exports.TSTypePredicate = alias("tsTypePredicate"),
TSTypeQuery = exports.TSTypeQuery = alias("tsTypeQuery"),
TSTypeLiteral = exports.TSTypeLiteral = alias("tsTypeLiteral"),
TSArrayType = exports.TSArrayType = alias("tsArrayType"),
TSTupleType = exports.TSTupleType = alias("tsTupleType"),
TSOptionalType = exports.TSOptionalType = alias("tsOptionalType"),
TSRestType = exports.TSRestType = alias("tsRestType"),
TSNamedTupleMember = exports.TSNamedTupleMember = alias("tsNamedTupleMember"),
TSUnionType = exports.TSUnionType = alias("tsUnionType"),
TSIntersectionType = exports.TSIntersectionType = alias("tsIntersectionType"),
TSConditionalType = exports.TSConditionalType = alias("tsConditionalType"),
TSInferType = exports.TSInferType = alias("tsInferType"),
TSParenthesizedType = exports.TSParenthesizedType = alias("tsParenthesizedType"),
TSTypeOperator = exports.TSTypeOperator = alias("tsTypeOperator"),
TSIndexedAccessType = exports.TSIndexedAccessType = alias("tsIndexedAccessType"),
TSMappedType = exports.TSMappedType = alias("tsMappedType"),
TSTemplateLiteralType = exports.TSTemplateLiteralType = alias("tsTemplateLiteralType"),
TSLiteralType = exports.TSLiteralType = alias("tsLiteralType"),
TSExpressionWithTypeArguments = exports.TSExpressionWithTypeArguments = alias("tsExpressionWithTypeArguments"),
TSInterfaceDeclaration = exports.TSInterfaceDeclaration = alias("tsInterfaceDeclaration"),
TSInterfaceBody = exports.TSInterfaceBody = alias("tsInterfaceBody"),
TSTypeAliasDeclaration = exports.TSTypeAliasDeclaration = alias("tsTypeAliasDeclaration"),
TSInstantiationExpression = exports.TSInstantiationExpression = alias("tsInstantiationExpression"),
TSAsExpression = exports.TSAsExpression = alias("tsAsExpression"),
TSSatisfiesExpression = exports.TSSatisfiesExpression = alias("tsSatisfiesExpression"),
TSTypeAssertion = exports.TSTypeAssertion = alias("tsTypeAssertion"),
TSEnumBody = exports.TSEnumBody = alias("tsEnumBody"),
TSEnumDeclaration = exports.TSEnumDeclaration = alias("tsEnumDeclaration"),
TSEnumMember = exports.TSEnumMember = alias("tsEnumMember"),
TSModuleDeclaration = exports.TSModuleDeclaration = alias("tsModuleDeclaration"),
TSModuleBlock = exports.TSModuleBlock = alias("tsModuleBlock"),
TSImportType = exports.TSImportType = alias("tsImportType"),
TSImportEqualsDeclaration = exports.TSImportEqualsDeclaration = alias("tsImportEqualsDeclaration"),
TSExternalModuleReference = exports.TSExternalModuleReference = alias("tsExternalModuleReference"),
TSNonNullExpression = exports.TSNonNullExpression = alias("tsNonNullExpression"),
TSExportAssignment = exports.TSExportAssignment = alias("tsExportAssignment"),
TSNamespaceExportDeclaration = exports.TSNamespaceExportDeclaration = alias("tsNamespaceExportDeclaration"),
TSTypeAnnotation = exports.TSTypeAnnotation = alias("tsTypeAnnotation"),
TSTypeParameterInstantiation = exports.TSTypeParameterInstantiation = alias("tsTypeParameterInstantiation"),
TSTypeParameterDeclaration = exports.TSTypeParameterDeclaration = alias("tsTypeParameterDeclaration"),
TSTypeParameter = exports.TSTypeParameter = alias("tsTypeParameter");
const NumberLiteral = exports.NumberLiteral = b.numberLiteral,
RegexLiteral = exports.RegexLiteral = b.regexLiteral,
RestProperty = exports.RestProperty = b.restProperty,
SpreadProperty = exports.SpreadProperty = b.spreadProperty;
//# sourceMappingURL=uppercase.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildUndefinedNode = buildUndefinedNode;
var _index = require("./generated/index.js");
function buildUndefinedNode() {
return (0, _index.unaryExpression)("void", (0, _index.numericLiteral)(0), true);
}
//# sourceMappingURL=productions.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_index","require","buildUndefinedNode","unaryExpression","numericLiteral"],"sources":["../../src/builders/productions.ts"],"sourcesContent":["import { numericLiteral, unaryExpression } from \"./generated/index.ts\";\n\nexport function buildUndefinedNode() {\n return unaryExpression(\"void\", numericLiteral(0), true);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAEO,SAASC,kBAAkBA,CAAA,EAAG;EACnC,OAAO,IAAAC,sBAAe,EAAC,MAAM,EAAE,IAAAC,qBAAc,EAAC,CAAC,CAAC,EAAE,IAAI,CAAC;AACzD","ignoreList":[]}
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = buildChildren;
var _index = require("../../validators/generated/index.js");
var _cleanJSXElementLiteralChild = require("../../utils/react/cleanJSXElementLiteralChild.js");
function buildChildren(node) {
const elements = [];
for (let i = 0; i < node.children.length; i++) {
let child = node.children[i];
if ((0, _index.isJSXText)(child)) {
(0, _cleanJSXElementLiteralChild.default)(child, elements);
continue;
}
if ((0, _index.isJSXExpressionContainer)(child)) child = child.expression;
if ((0, _index.isJSXEmptyExpression)(child)) continue;
elements.push(child);
}
return elements;
}
//# sourceMappingURL=buildChildren.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_index","require","_cleanJSXElementLiteralChild","buildChildren","node","elements","i","children","length","child","isJSXText","cleanJSXElementLiteralChild","isJSXExpressionContainer","expression","isJSXEmptyExpression","push"],"sources":["../../../src/builders/react/buildChildren.ts"],"sourcesContent":["import {\n isJSXText,\n isJSXExpressionContainer,\n isJSXEmptyExpression,\n} from \"../../validators/generated/index.ts\";\nimport cleanJSXElementLiteralChild from \"../../utils/react/cleanJSXElementLiteralChild.ts\";\nimport type * as t from \"../../index.ts\";\n\ntype ReturnedChild =\n | t.JSXSpreadChild\n | t.JSXElement\n | t.JSXFragment\n | t.Expression;\n\nexport default function buildChildren(\n node: t.JSXElement | t.JSXFragment,\n): ReturnedChild[] {\n const elements = [];\n\n for (let i = 0; i < node.children.length; i++) {\n let child: any = node.children[i];\n\n if (isJSXText(child)) {\n cleanJSXElementLiteralChild(child, elements);\n continue;\n }\n\n if (isJSXExpressionContainer(child)) child = child.expression;\n if (isJSXEmptyExpression(child)) continue;\n\n elements.push(child);\n }\n\n return elements;\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAKA,IAAAC,4BAAA,GAAAD,OAAA;AASe,SAASE,aAAaA,CACnCC,IAAkC,EACjB;EACjB,MAAMC,QAAQ,GAAG,EAAE;EAEnB,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGF,IAAI,CAACG,QAAQ,CAACC,MAAM,EAAEF,CAAC,EAAE,EAAE;IAC7C,IAAIG,KAAU,GAAGL,IAAI,CAACG,QAAQ,CAACD,CAAC,CAAC;IAEjC,IAAI,IAAAI,gBAAS,EAACD,KAAK,CAAC,EAAE;MACpB,IAAAE,oCAA2B,EAACF,KAAK,EAAEJ,QAAQ,CAAC;MAC5C;IACF;IAEA,IAAI,IAAAO,+BAAwB,EAACH,KAAK,CAAC,EAAEA,KAAK,GAAGA,KAAK,CAACI,UAAU;IAC7D,IAAI,IAAAC,2BAAoB,EAACL,KAAK,CAAC,EAAE;IAEjCJ,QAAQ,CAACU,IAAI,CAACN,KAAK,CAAC;EACtB;EAEA,OAAOJ,QAAQ;AACjB","ignoreList":[]}
@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = createTSUnionType;
var _index = require("../generated/index.js");
var _removeTypeDuplicates = require("../../modifications/typescript/removeTypeDuplicates.js");
var _index2 = require("../../validators/generated/index.js");
function createTSUnionType(typeAnnotations) {
const types = typeAnnotations.map(type => {
return (0, _index2.isTSTypeAnnotation)(type) ? type.typeAnnotation : type;
});
const flattened = (0, _removeTypeDuplicates.default)(types);
if (flattened.length === 1) {
return flattened[0];
} else {
return (0, _index.tsUnionType)(flattened);
}
}
//# sourceMappingURL=createTSUnionType.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_index","require","_removeTypeDuplicates","_index2","createTSUnionType","typeAnnotations","types","map","type","isTSTypeAnnotation","typeAnnotation","flattened","removeTypeDuplicates","length","tsUnionType"],"sources":["../../../src/builders/typescript/createTSUnionType.ts"],"sourcesContent":["import { tsUnionType } from \"../generated/index.ts\";\nimport removeTypeDuplicates from \"../../modifications/typescript/removeTypeDuplicates.ts\";\nimport { isTSTypeAnnotation } from \"../../validators/generated/index.ts\";\nimport type * as t from \"../../index.ts\";\n\n/**\n * Takes an array of `types` and flattens them, removing duplicates and\n * returns a `UnionTypeAnnotation` node containing them.\n */\nexport default function createTSUnionType(\n typeAnnotations: (t.TSTypeAnnotation | t.TSType)[],\n): t.TSType {\n const types = typeAnnotations.map(type => {\n return isTSTypeAnnotation(type) ? type.typeAnnotation : type;\n });\n const flattened = removeTypeDuplicates(types);\n\n if (flattened.length === 1) {\n return flattened[0];\n } else {\n return tsUnionType(flattened);\n }\n}\n"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,qBAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AAOe,SAASG,iBAAiBA,CACvCC,eAAkD,EACxC;EACV,MAAMC,KAAK,GAAGD,eAAe,CAACE,GAAG,CAACC,IAAI,IAAI;IACxC,OAAO,IAAAC,0BAAkB,EAACD,IAAI,CAAC,GAAGA,IAAI,CAACE,cAAc,GAAGF,IAAI;EAC9D,CAAC,CAAC;EACF,MAAMG,SAAS,GAAG,IAAAC,6BAAoB,EAACN,KAAK,CAAC;EAE7C,IAAIK,SAAS,CAACE,MAAM,KAAK,CAAC,EAAE;IAC1B,OAAOF,SAAS,CAAC,CAAC,CAAC;EACrB,CAAC,MAAM;IACL,OAAO,IAAAG,kBAAW,EAACH,SAAS,CAAC;EAC/B;AACF","ignoreList":[]}
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = validateNode;
var _validate = require("../validators/validate.js");
var _index = require("../index.js");
function validateNode(node) {
if (node == null || typeof node !== "object") return;
const fields = _index.NODE_FIELDS[node.type];
if (!fields) return;
const keys = _index.BUILDER_KEYS[node.type];
for (const key of keys) {
const field = fields[key];
if (field != null) (0, _validate.validateInternal)(field, node, key, node[key]);
}
return node;
}
//# sourceMappingURL=validateNode.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_validate","require","_index","validateNode","node","fields","NODE_FIELDS","type","keys","BUILDER_KEYS","key","field","validateInternal"],"sources":["../../src/builders/validateNode.ts"],"sourcesContent":["import { validateInternal } from \"../validators/validate.ts\";\nimport type * as t from \"../index.ts\";\nimport { BUILDER_KEYS, NODE_FIELDS } from \"../index.ts\";\n\nexport default function validateNode<N extends t.Node>(node: N) {\n if (node == null || typeof node !== \"object\") return;\n const fields = NODE_FIELDS[node.type];\n if (!fields) return;\n\n // todo: because keys not in BUILDER_KEYS are not validated - this actually allows invalid nodes in some cases\n const keys = BUILDER_KEYS[node.type] as (keyof N & string)[];\n for (const key of keys) {\n const field = fields[key];\n if (field != null) validateInternal(field, node, key, node[key]);\n }\n return node;\n}\n"],"mappings":";;;;;;AAAA,IAAAA,SAAA,GAAAC,OAAA;AAEA,IAAAC,MAAA,GAAAD,OAAA;AAEe,SAASE,YAAYA,CAAmBC,IAAO,EAAE;EAC9D,IAAIA,IAAI,IAAI,IAAI,IAAI,OAAOA,IAAI,KAAK,QAAQ,EAAE;EAC9C,MAAMC,MAAM,GAAGC,kBAAW,CAACF,IAAI,CAACG,IAAI,CAAC;EACrC,IAAI,CAACF,MAAM,EAAE;EAGb,MAAMG,IAAI,GAAGC,mBAAY,CAACL,IAAI,CAACG,IAAI,CAAyB;EAC5D,KAAK,MAAMG,GAAG,IAAIF,IAAI,EAAE;IACtB,MAAMG,KAAK,GAAGN,MAAM,CAACK,GAAG,CAAC;IACzB,IAAIC,KAAK,IAAI,IAAI,EAAE,IAAAC,0BAAgB,EAACD,KAAK,EAAEP,IAAI,EAAEM,GAAG,EAAEN,IAAI,CAACM,GAAG,CAAC,CAAC;EAClE;EACA,OAAON,IAAI;AACb","ignoreList":[]}
+12
View File
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = clone;
var _cloneNode = require("./cloneNode.js");
function clone(node) {
return (0, _cloneNode.default)(node, false);
}
//# sourceMappingURL=clone.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_cloneNode","require","clone","node","cloneNode"],"sources":["../../src/clone/clone.ts"],"sourcesContent":["import cloneNode from \"./cloneNode.ts\";\nimport type * as t from \"../index.ts\";\n\n/**\n * Create a shallow clone of a `node`, including only\n * properties belonging to the node.\n * @deprecated Use t.cloneNode instead.\n */\nexport default function clone<T extends t.Node>(node: T): T {\n return cloneNode(node, /* deep */ false);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AAQe,SAASC,KAAKA,CAAmBC,IAAO,EAAK;EAC1D,OAAO,IAAAC,kBAAS,EAACD,IAAI,EAAa,KAAK,CAAC;AAC1C","ignoreList":[]}
+12
View File
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = cloneDeep;
var _cloneNode = require("./cloneNode.js");
function cloneDeep(node) {
return (0, _cloneNode.default)(node);
}
//# sourceMappingURL=cloneDeep.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_cloneNode","require","cloneDeep","node","cloneNode"],"sources":["../../src/clone/cloneDeep.ts"],"sourcesContent":["import cloneNode from \"./cloneNode.ts\";\nimport type * as t from \"../index.ts\";\n\n/**\n * Create a deep clone of a `node` and all of it's child nodes\n * including only properties belonging to the node.\n * @deprecated Use t.cloneNode instead.\n */\nexport default function cloneDeep<T extends t.Node>(node: T): T {\n return cloneNode(node);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AAQe,SAASC,SAASA,CAAmBC,IAAO,EAAK;EAC9D,OAAO,IAAAC,kBAAS,EAACD,IAAI,CAAC;AACxB","ignoreList":[]}
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = cloneDeepWithoutLoc;
var _cloneNode = require("./cloneNode.js");
function cloneDeepWithoutLoc(node) {
return (0, _cloneNode.default)(node, true, true);
}
//# sourceMappingURL=cloneDeepWithoutLoc.js.map
@@ -0,0 +1 @@
{"version":3,"names":["_cloneNode","require","cloneDeepWithoutLoc","node","cloneNode"],"sources":["../../src/clone/cloneDeepWithoutLoc.ts"],"sourcesContent":["import cloneNode from \"./cloneNode.ts\";\nimport type * as t from \"../index.ts\";\n/**\n * Create a deep clone of a `node` and all of it's child nodes\n * including only properties belonging to the node.\n * excluding `_private` and location properties.\n */\nexport default function cloneDeepWithoutLoc<T extends t.Node>(node: T): T {\n return cloneNode(node, /* deep */ true, /* withoutLoc */ true);\n}\n"],"mappings":";;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AAOe,SAASC,mBAAmBA,CAAmBC,IAAO,EAAK;EACxE,OAAO,IAAAC,kBAAS,EAACD,IAAI,EAAa,IAAI,EAAmB,IAAI,CAAC;AAChE","ignoreList":[]}
+107
View File
@@ -0,0 +1,107 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = cloneNode;
var _index = require("../definitions/index.js");
var _index2 = require("../validators/generated/index.js");
const {
hasOwn
} = {
hasOwn: Function.call.bind(Object.prototype.hasOwnProperty)
};
function cloneIfNode(obj, deep, withoutLoc, commentsCache) {
if (obj && typeof obj.type === "string") {
return cloneNodeInternal(obj, deep, withoutLoc, commentsCache);
}
return obj;
}
function cloneIfNodeOrArray(obj, deep, withoutLoc, commentsCache) {
if (Array.isArray(obj)) {
return obj.map(node => cloneIfNode(node, deep, withoutLoc, commentsCache));
}
return cloneIfNode(obj, deep, withoutLoc, commentsCache);
}
function cloneNode(node, deep = true, withoutLoc = false) {
return cloneNodeInternal(node, deep, withoutLoc, new Map());
}
function cloneNodeInternal(node, deep = true, withoutLoc = false, commentsCache) {
if (!node) return node;
const {
type
} = node;
const newNode = {
type: node.type
};
if ((0, _index2.isIdentifier)(node)) {
newNode.name = node.name;
if (hasOwn(node, "optional") && typeof node.optional === "boolean") {
newNode.optional = node.optional;
}
if (hasOwn(node, "typeAnnotation")) {
newNode.typeAnnotation = deep ? cloneIfNodeOrArray(node.typeAnnotation, true, withoutLoc, commentsCache) : node.typeAnnotation;
}
if (hasOwn(node, "decorators")) {
newNode.decorators = deep ? cloneIfNodeOrArray(node.decorators, true, withoutLoc, commentsCache) : node.decorators;
}
} else if (!hasOwn(_index.NODE_FIELDS, type)) {
throw new Error(`Unknown node type: "${type}"`);
} else {
for (const field of Object.keys(_index.NODE_FIELDS[type])) {
if (hasOwn(node, field)) {
if (deep) {
newNode[field] = (0, _index2.isFile)(node) && field === "comments" ? maybeCloneComments(node.comments, deep, withoutLoc, commentsCache) : cloneIfNodeOrArray(node[field], true, withoutLoc, commentsCache);
} else {
newNode[field] = node[field];
}
}
}
}
if (hasOwn(node, "loc")) {
if (withoutLoc) {
newNode.loc = null;
} else {
newNode.loc = node.loc;
}
}
if (hasOwn(node, "leadingComments")) {
newNode.leadingComments = maybeCloneComments(node.leadingComments, deep, withoutLoc, commentsCache);
}
if (hasOwn(node, "innerComments")) {
newNode.innerComments = maybeCloneComments(node.innerComments, deep, withoutLoc, commentsCache);
}
if (hasOwn(node, "trailingComments")) {
newNode.trailingComments = maybeCloneComments(node.trailingComments, deep, withoutLoc, commentsCache);
}
if (hasOwn(node, "extra")) {
newNode.extra = Object.assign({}, node.extra);
}
return newNode;
}
function maybeCloneComments(comments, deep, withoutLoc, commentsCache) {
if (!comments || !deep) {
return comments;
}
return comments.map(comment => {
const cache = commentsCache.get(comment);
if (cache) return cache;
const {
type,
value,
loc
} = comment;
const ret = {
type,
value,
loc
};
if (withoutLoc) {
ret.loc = null;
}
commentsCache.set(comment, ret);
return ret;
});
}
//# sourceMappingURL=cloneNode.js.map
File diff suppressed because one or more lines are too long

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