├── .nvmrc ├── app ├── favicon.ico ├── robots.ts ├── ai │ └── page.tsx ├── image │ └── page.tsx ├── globals.css ├── sitemap.ts ├── utils │ ├── tryParseJson.ts │ └── fetchToCurl.ts ├── components │ ├── FriendLinks.tsx │ ├── Navigation.tsx │ ├── GitHubLink.tsx │ ├── JsonLd.tsx │ ├── KeywordOptimization.tsx │ ├── RelatedTools.tsx │ ├── PerformanceMonitor.tsx │ ├── QuickTip.tsx │ ├── JsonParser.tsx │ ├── VisitorStats.tsx │ └── FetchToCurl.tsx ├── api │ ├── video │ │ └── convert │ │ │ └── route.ts │ ├── convert │ │ └── route.ts │ └── og │ │ └── route.tsx ├── metadata.ts ├── layout.tsx ├── video │ └── page.tsx └── page.tsx ├── husky └── pre-commit ├── public ├── vercel.svg ├── sitemap.xml ├── baidusitemap.xml ├── robots.txt ├── window.svg ├── file.svg ├── globe.svg ├── sitemap-0.xml └── next.svg ├── dv.md ├── urls.txt ├── postcss.config.mjs ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── ecosystem.config.js ├── eslint.config.mjs ├── tailwind.config.ts ├── .gitignore ├── tsconfig.json ├── next-sitemap.config.js ├── next.config.ts ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxingheng/text-escape/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run version-auto -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dv.md: -------------------------------------------------------------------------------- 1 | curl -H 'Content-Type:text/plain' --data-binary @urls.txt "http://data.zz.baidu.com/urls?site=https://text-escape.jcommon.top&token=Ce9lijsmmj0KydIb" -------------------------------------------------------------------------------- /urls.txt: -------------------------------------------------------------------------------- 1 | https://text-escape.jcommon.top 2 | https://text-escape.jcommon.top/api/og 3 | https://text-escape.jcommon.top/en 4 | https://text-escape.jcommon.top/video -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "stylelint.vscode-stylelint", 6 | "visualstudioexptteam.vscodeintellicode" 7 | ] 8 | } -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://text-escape.jcommon.top/sitemap-0.xml 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /public/baidusitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://text-escape.jcommon.top 5 | 2024-03-21 6 | daily 7 | 1.0 8 | 9 | -------------------------------------------------------------------------------- /app/robots.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from 'next'; 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | return { 5 | rules: { 6 | userAgent: '*', 7 | allow: '/', 8 | disallow: ['/api/*'], 9 | }, 10 | sitemap: 'https://text-escape.jcommon.top/sitemap.xml', 11 | }; 12 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Allow: / 4 | Disallow: /api/ 5 | Disallow: /admin/ 6 | 7 | # Baiduspider 8 | User-agent: Baiduspider 9 | Allow: / 10 | Crawl-delay: 1 11 | 12 | # Host 13 | Host: https://text-escape.jcommon.top 14 | 15 | # Sitemaps 16 | Sitemap: https://text-escape.jcommon.top/sitemap.xml 17 | -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [{ 3 | name: 'text-escape', 4 | script: 'node_modules/next/dist/bin/next', 5 | args: 'start', 6 | instances: 1, 7 | autorestart: true, 8 | watch: false, 9 | max_memory_restart: '1G', 10 | env: { 11 | NODE_ENV: 'production', 12 | PORT: process.env.PORT || 3005 13 | } 14 | }] 15 | } -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/ai/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function AiPage() { 4 | return ( 5 |
6 |

AI 工具

7 |

8 | 这里将会集成各种实用的 AI 工具,敬请期待! 9 |

10 | {/* 你可以在这里添加更多 AI 相关的功能和内容 */} 11 |
12 | ); 13 | } -------------------------------------------------------------------------------- /app/image/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function ImagePage() { 4 | return ( 5 |
6 |

图片工具

7 |

8 | 这里将会集成各种实用的图片处理工具,敬请期待! 9 |

10 | {/* 你可以在这里添加更多图片相关的功能和内容 */} 11 |
12 | ); 13 | } -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } satisfies Config; 19 | -------------------------------------------------------------------------------- /app/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from 'next'; 2 | 3 | export default function sitemap(): MetadataRoute.Sitemap { 4 | const baseUrl = 'https://text-escape.jcommon.top'; 5 | 6 | return [ 7 | { 8 | url: baseUrl, 9 | lastModified: new Date(), 10 | changeFrequency: 'daily', 11 | priority: 1, 12 | }, 13 | { 14 | url: `${baseUrl}/video`, 15 | lastModified: new Date(), 16 | changeFrequency: 'weekly', 17 | priority: 0.8, 18 | }, 19 | ]; 20 | } -------------------------------------------------------------------------------- /app/utils/tryParseJson.ts: -------------------------------------------------------------------------------- 1 | export function tryParseJson(str: string): string { 2 | let result = str; 3 | try { 4 | result = JSON.parse(str); 5 | } catch { 6 | try { 7 | const start = str.indexOf("```json\n"); 8 | const end = str.lastIndexOf("\n```"); 9 | if (start !== -1 && end !== -1) { 10 | result = JSON.parse(str.substring(start + 8, end)); 11 | } 12 | } catch (err) { 13 | console.warn("tryParseJson error", err); 14 | } 15 | } 16 | return typeof result === "string" ? result : JSON.stringify(result, null, 2); 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | yarn.lock 44 | 45 | package-lock.json -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /app/components/FriendLinks.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | export default function FriendLinks() { 4 | const links = [ 5 | { 6 | name: 'Markdown转图片工具', 7 | url: 'https://markdown-to-image-serve.jcommon.top', 8 | icon: '📝' 9 | }, 10 | // 可以在这里添加更多友情链接 11 | ] 12 | 13 | return ( 14 |
15 | 友情链接: 16 | {links.map((link) => ( 17 | 24 | {link.icon} 25 | {link.name} 26 | 27 | ))} 28 |
29 | ) 30 | } -------------------------------------------------------------------------------- /next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next-sitemap').IConfig} */ 2 | module.exports = { 3 | siteUrl: 'https://text-escape.jcommon.top', 4 | generateRobotsTxt: true, 5 | changefreq: 'daily', 6 | priority: 1.0, 7 | sitemapSize: 7000, 8 | 9 | transform: async (config, path) => { 10 | // 根据路径自定义优先级 11 | const priority = path === '/' ? 1.0 : 0.8 12 | const changefreq = path === '/' ? 'daily' : 'weekly' 13 | 14 | return { 15 | loc: path, 16 | changefreq, 17 | priority, 18 | lastmod: new Date().toISOString(), 19 | } 20 | }, 21 | 22 | robotsTxtOptions: { 23 | policies: [ 24 | { 25 | userAgent: '*', 26 | allow: '/', 27 | disallow: ['/api/', '/admin/'], 28 | }, 29 | // 针对百度爬虫的特殊规则 30 | { 31 | userAgent: 'Baiduspider', 32 | allow: '/', 33 | crawlDelay: 1, 34 | } 35 | ], 36 | }, 37 | } -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/sitemap-0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://text-escape.jcommon.top/robots.txt2025-05-09T03:09:32.481Zweekly0.8 4 | https://text-escape.jcommon.top2025-05-09T03:09:32.482Zdaily1 5 | https://text-escape.jcommon.top/sitemap.xml2025-05-09T03:09:32.482Zweekly0.8 6 | https://text-escape.jcommon.top/video2025-05-09T03:09:32.482Zweekly0.8 7 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | // import { withContentlayer } from 'next-contentlayer' 3 | 4 | const nextConfig: NextConfig = { 5 | /* config options here */ 6 | headers: async () => { 7 | return [ 8 | { 9 | source: '/:path*', 10 | headers: [ 11 | { 12 | key: 'X-Robots-Tag', 13 | value: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1', 14 | }, 15 | { 16 | key: 'X-Content-Type-Options', 17 | value: 'nosniff', 18 | }, 19 | { 20 | key: 'X-Frame-Options', 21 | value: 'DENY', 22 | }, 23 | { 24 | key: 'X-XSS-Protection', 25 | value: '1; mode=block', 26 | } 27 | ], 28 | }, 29 | ] 30 | }, 31 | poweredByHeader: false, 32 | compress: true, 33 | images: { 34 | domains: ['text-escape.jcommon.top'], 35 | formats: ['image/avif', 'image/webp'], 36 | deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], 37 | }, 38 | }; 39 | 40 | export default nextConfig 41 | -------------------------------------------------------------------------------- /app/components/Navigation.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import Link from 'next/link' 4 | import { usePathname } from 'next/navigation' 5 | 6 | export default function Navigation() { 7 | const pathname = usePathname() 8 | 9 | const navItems = [ 10 | { name: '文本工具', path: '/' }, 11 | { name: 'AI 工具', path: '/ai' }, 12 | { name: '图片工具', path: '/image' }, 13 | { name: '视频工具', path: '/video' }, 14 | ] 15 | 16 | return ( 17 | 36 | ) 37 | } -------------------------------------------------------------------------------- /app/components/GitHubLink.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | export default function GitHubLink() { 4 | return ( 5 | 12 | 23 | 24 | ); 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "text-escape", 3 | "version": "1.1.3", 4 | "private": true, 5 | "description": "一个简单易用的在线文本转义工具,支持换行符和双引号的转义与反转义", 6 | "author": "wxingheng", 7 | "scripts": { 8 | "dev": "next dev --turbopack -p 3004", 9 | "build": "next build", 10 | "start": "next start -p ${PORT:-3005}", 11 | "lint": "next lint", 12 | "postbuild": "next-sitemap", 13 | "version-auto": "jcommon-node -va -t", 14 | "pm2:start": "pm2 start ecosystem.config.js", 15 | "pm2:stop": "pm2 stop text-escape", 16 | "pm2:restart": "pm2 restart text-escape", 17 | "pm2:logs": "pm2 logs text-escape", 18 | "pm2:status": "pm2 status" 19 | }, 20 | "dependencies": { 21 | "next": "15.1.2", 22 | "react": "^19.0.0", 23 | "react-dom": "^19.0.0", 24 | "tailwindcss": "^3.4.1" 25 | }, 26 | "devDependencies": { 27 | "jcommon-node": "^1.0.14", 28 | "@eslint/eslintrc": "^3", 29 | "@types/node": "^20", 30 | "@types/react": "^19", 31 | "@types/react-dom": "^19", 32 | "eslint": "^9", 33 | "eslint-config-next": "15.1.2", 34 | "next-sitemap": "^4.2.3", 35 | "postcss": "^8", 36 | "typescript": "^5" 37 | }, 38 | "engines": { 39 | "node": "20" 40 | }, 41 | "time": "250401202509" 42 | } 43 | -------------------------------------------------------------------------------- /app/utils/fetchToCurl.ts: -------------------------------------------------------------------------------- 1 | interface RequestOptions extends RequestInit { 2 | url: string; 3 | } 4 | 5 | export function fetchToCurl(options: RequestOptions): string { 6 | const { 7 | method = 'GET', 8 | headers = {}, 9 | body, 10 | url, 11 | } = options; 12 | 13 | // 开始构建 curl 命令 14 | let curlCommand = `curl '${url}'`; 15 | 16 | // 添加请求方法 17 | if (method !== 'GET') { 18 | curlCommand += ` -X ${method}`; 19 | } 20 | 21 | // 添加请求头 22 | Object.entries(headers).forEach(([key, value]) => { 23 | curlCommand += ` \\\n -H '${key}: ${value}'`; 24 | }); 25 | 26 | // 添加请求体 27 | if (body) { 28 | let bodyStr = ''; 29 | if (typeof body === 'string') { 30 | bodyStr = body; 31 | } else if (body instanceof URLSearchParams) { 32 | bodyStr = body.toString(); 33 | } else if (body instanceof FormData) { 34 | const formDataObj: Record = {}; 35 | body.forEach((value, key) => { 36 | formDataObj[key] = value.toString(); 37 | }); 38 | bodyStr = JSON.stringify(formDataObj); 39 | } else if (body instanceof Blob) { 40 | bodyStr = '[Blob data]'; 41 | } else { 42 | bodyStr = JSON.stringify(body); 43 | } 44 | curlCommand += ` \\\n -d '${bodyStr}'`; 45 | } 46 | 47 | return curlCommand; 48 | } -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/components/JsonLd.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | // import { usePathname } from 'next/navigation'; // 移除未使用的导入 4 | 5 | export default function JsonLd() { 6 | // const pathname = usePathname(); // 移除未使用变量 7 | 8 | const jsonLd = { 9 | '@context': 'https://schema.org', 10 | '@type': 'WebApplication', 11 | name: '文本转义工具', 12 | description: '一个简单易用的在线文本转义工具,支持换行符和双引号的转义与反转义,提供实时预览和一键复制功能。', 13 | url: 'https://text-escape.jcommon.top', 14 | applicationCategory: 'DeveloperApplication', 15 | operatingSystem: 'Any', 16 | browserRequirements: 'Requires JavaScript. Requires HTML5.', 17 | offers: { 18 | '@type': 'Offer', 19 | price: '0', 20 | priceCurrency: 'CNY' 21 | }, 22 | author: { 23 | '@type': 'Person', 24 | name: 'jcommon', 25 | url: 'https://github.com/wxingheng' 26 | }, 27 | publisher: { 28 | '@type': 'Organization', 29 | name: 'jcommon', 30 | url: 'https://github.com/wxingheng' 31 | }, 32 | mainEntityOfPage: { 33 | '@type': 'WebPage', 34 | '@id': 'https://text-escape.jcommon.top' 35 | }, 36 | potentialAction: { 37 | '@type': 'UseAction', 38 | target: 'https://text-escape.jcommon.top' 39 | } 40 | }; 41 | 42 | return ( 43 | 121 |