# Scripting 文档站点项目 ## 项目介绍 这是 **Scripting App** 的官方文档网站生成器项目。Scripting 是一款 iOS 应用程序,允许开发者使用 **TypeScript/TSX**(类 React 语法)编写 UI 组件和脚本,这些代码会被编译为原生 iOS 界面(基于 SwiftUI)。该项目基于 Rspress 静态站点生成器,从结构化的 JSON 配置和 Markdown 文件自动生成中英文双语文档网站,为 Scripting 平台提供完整的 API 参考、教程指南和版本更新日志。 项目的核心功能包括:通过自动化脚本从 `Scripting Documentation` 资源包读取文档配置,生成 325+ 个 API 文档页面;支持中英文双语切换和搜索;提供 RSS 订阅源用于更新日志;自动部署到 GitHub Pages。文档涵盖了 Scripting 的所有功能模块,包括交互式组件(Widget、Live Activity、Control Widget)、设备能力访问(蓝牙、健康数据、位置、相机等 25+ 个 API)、50+ 个 UI 视图组件、以及 AI 助手集成等高级特性。 ## 核心 API 和功能 ### 文档自动生成脚本 自动从 JSON 配置文件生成完整的双语文档站点 ```javascript // scripts/docs.js - 文档生成器核心逻辑 import fs from "fs"; import path from "path"; const resourcePath = path.join(__dirname, "..", "Scripting Documentation"); const docsPath = path.join(__dirname, "..", "docs"); // 处理文档项,支持文件夹、README 和示例代码 const processDocItem = (item, parentPath = "", language = "en") => { const { pro, title, subtitle, keywords, example, readme, children } = item; const folderName = title.en; const basePath = path.join(docsPath, language, "guide", "doc_v2", parentPath, folderName); // 生成 _meta.json 导航配置 const metaPath = path.join(docsPath, language, "guide", "doc_v2", parentPath, "_meta.json"); if (children && children.length > 0) { // 递归处理子项 children.forEach(child => processDocItem(child, path.join(parentPath, folderName), language) ); } else { if (readme) { // 读取 Markdown 文档 const readmePath = path.join(resourcePath, readme, language + ".md"); const readmeContent = fs.readFileSync(readmePath, "utf-8"); const readmeMd = `# ${title[language]}\n\n${readmeContent}`; writeFile(basePath + ".mdx", readmeMd); } if (example) { // 读取 TSX 示例代码 const tsxContent = fs.readFileSync(path.join(resourcePath, example + ".tsx"), "utf-8"); const exampleMd = `# ${language === "en" ? "Example" : "示例"}\n\n\`\`\`tsx\n${tsxContent}\n\`\`\``; writeFile(path.join(basePath, exampleName + ".mdx"), exampleMd); } } }; // 处理双语版本 const processLanguages = (docItem) => { processDocItem(docItem, "", "en"); processDocItem(docItem, "", "zh"); }; // 主执行流程 const processDocs = () => { const docJson = JSON.parse(fs.readFileSync(path.join(resourcePath, "doc.json"), "utf-8")); docJson.forEach(item => processLanguages(item)); }; processDocs(); // 运行命令: // bun run generate:docs // 清空现有文档目录并重新生成所有 325+ 个文档文件 ``` ### Rspress 站点配置 配置双语文档站点、RSS 订阅和 GitHub Pages 部署 ```typescript // rspress.config.ts - 站点核心配置 import { pluginRss } from "@rspress/plugin-rss"; import { defineConfig } from "rspress/config"; import ghPages from "rspress-plugin-gh-pages"; const siteUrl = "https://scriptingapp.github.io"; export default defineConfig({ title: "Scripting", icon: "/icon.png", logo: "/logo.png", logoText: "Scripting", // 路由配置 route: { cleanUrls: true, extensions: [".md", ".mdx"], }, // 搜索配置 - 支持代码块搜索 search: { codeBlocks: true, }, // Markdown 配置 markdown: { showLineNumbers: true, defaultWrapCode: false, }, // 插件配置 plugins: [ // GitHub Pages 自动部署 ghPages({ repo: "https://github.com/ScriptingApp/ScriptingApp.github.io.git", branch: "deploy", }), // RSS 订阅源 - 中英文更新日志 pluginRss({ siteUrl: siteUrl, feed: [ { id: "changelog", test: "/guide/changelog/", title: "Scripting Changelog", language: "en-US", }, { id: "changelog_zh", test: "/zh/guide/changelog/", title: "Scripting 更新日志", language: "zh-CN", }, ], output: { dir: "feeds", type: "rss", }, }), ], // 主题配置 - 双语支持 themeConfig: { locales: [ { lang: "en", label: "English", searchPlaceholderText: "Search", searchNoResultsText: "No results for", }, { lang: "zh", label: "简体中文", searchPlaceholderText: "搜索", searchNoResultsText: "没有搜索结果", }, ], // 社交链接 socialLinks: [ { icon: "github", mode: "link", content: "https://github.com/ScriptingApp", }, { icon: "X", mode: "link", content: "https://x.com/thomfang", }, // RSS 订阅链接(英文和中文) ], }, // 语言配置 lang: "en", locales: [ { lang: "en", label: "English", title: "Scripting", description: "Build native iOS apps with TypeScript", }, { lang: "zh", label: "简体中文", title: "Scripting", description: "使用 TypeScript 构建原生 iOS 应用", }, ], }); // 构建命令: // bun run build (生产构建,分配 16GB 内存) // bun run dev (开发服务器) ``` ### NPM 脚本和依赖管理 项目构建、开发和文档生成的完整工作流 ```json { "name": "scripting-doc", "version": "1.0.0", "private": true, "scripts": { "setup": "bun install", "dev": "rspress dev", "build": "NODE_OPTIONS='--max-old-space-size=16384' rspress build", "build:fun": "NODE_OPTIONS='--max-old-space-size=16384' rspress build -c rspress.config.fun.ts", "generate:docs": "rm -rf docs/en/guide/doc_v2/* && rm -rf docs/zh/guide/doc_v2/* && bun scripts/docs.js" }, "dependencies": { "rspress": "^1.45.6" }, "devDependencies": { "@rspress/plugin-rss": "^1.45.6", "@types/node": "^24.7.2", "rspress-plugin-gh-pages": "^0.3.0" } } // 常用命令: // bun run setup - 安装所有依赖 // bun run generate:docs - 从源文件重新生成所有文档 // bun run dev - 启动开发服务器(热重载) // bun run build - 构建生产版本(输出到 doc_build/) ``` ### GitHub Actions 自动部署 持续集成和自动部署到 GitHub Pages ```yaml # .github/workflows/deploy.yml name: Deploy to GitHub Pages on: push: branches: [main] workflow_dispatch: permissions: contents: read pages: write id-token: write jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Bun uses: oven-sh/setup-bun@v1 with: bun-version: latest - name: Install dependencies run: bun install - name: Build with Rspress run: NODE_OPTIONS='--max-old-space-size=16384' bun run build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ./doc_build deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 # 工作流说明: # 1. 当代码推送到 main 分支时自动触发 # 2. 使用 Bun 作为包管理器进行构建 # 3. 分配 16GB 内存进行大规模文档构建 # 4. 构建产物上传到 GitHub Pages # 5. 自动部署到 https://scriptingapp.github.io ``` ### Script API - 脚本运行时和元数据 管理脚本执行环境、生命周期和 URL Scheme ```typescript // Script 模块示例 - docs/en/guide/doc_v2/Script.mdx // 1. 获取脚本运行环境 if (Script.env === "widget") { // 在 Home Screen Widget 中运行(入口: widget.tsx) Widget.present(); } else if (Script.env === "index") { // 在主应用中运行(入口: index.tsx) Navigation.present({ element: }); } else if (Script.env === "control_widget") { // 在 Control Center 控件中运行(iOS 18+) ControlWidget.present(); } else if (Script.env === "notification") { // 在通知扩展中运行(入口: notification.tsx) Notification.present(); } else if (Script.env === "app_intents") { // 在 AppIntents 扩展中运行(入口: app_intents.tsx) // 处理 Widget/LiveActivity 的交互逻辑 } else if (Script.env === "keyboard") { // 在自定义键盘中运行(入口: keyboard.tsx) CustomKeyboard.present(); } // 2. 读取脚本元数据 console.log(Script.name); // "MyScript" console.log(Script.directory); // "/private/var/mobile/..." console.log(Script.metadata.version); // "1.2.0" console.log(Script.metadata.localizedName); // "天气助手" console.log(Script.metadata.author.name); // "John Doe" // 3. 处理 URL Scheme 参数 // URL: scripting://run/MyScript?user=John&id=123 console.log(Script.queryParameters.user); // "John" console.log(Script.queryParameters.id); // "123" // 4. 运行其他脚本并获取返回值 const result = await Script.run({ name: "ProcessData", queryParameters: { input: "abc" }, singleMode: true // 确保只有一个实例运行 }); console.log(result); // 从 Script.exit(result) 返回的数据 // 5. 终止脚本并返回结果 Script.exit("Done"); // 或返回结构化数据给 Shortcuts Script.exit(Intent.json({ status: "ok", data: processedData })); // 6. 创建 URL Schemes // 运行脚本 const runUrl = Script.createRunURLScheme("MyScript", { user: "Alice" }); // "scripting://run/MyScript?user=Alice" // 单实例运行 const singleUrl = Script.createRunSingleURLScheme("MyScript", { id: "1" }); // "scripting://run_single/MyScript?id=1" // 打开脚本编辑器 const openUrl = Script.createOpenURLScheme("MyScript"); // "scripting://open/MyScript" // 打开文档页面 const docUrl = Script.createDocumentationURLScheme("Widgets"); // "scripting://doc?title=Widgets" // 导入脚本 const importUrl = Script.createImportScriptsURLScheme([ "https://github.com/schl3ck/scripting-app-lib", "https://example.com/my-script.zip", ]); // "scripting://import_scripts?urls=..." // 7. 检查 PRO 功能访问权限 if (Script.hasFullAccess()) { // 使用 Scripting PRO 功能 await Assistant.requestStructuredData(prompt, schema); } ``` ### AppIntent - 交互式 Widget 和控件 注册和管理 Widget、Live Activity 和 Control Widget 的交互逻辑 ```typescript // app_intents.tsx - AppIntent 注册文件 // 1. 注册一个简单的 AppIntent export const RefreshDataIntent = AppIntentManager.register({ name: "RefreshDataIntent", protocol: AppIntentProtocol.AppIntent, // 通用操作 perform: async () => { // 执行数据刷新逻辑 const newData = await fetchDataFromAPI(); await Storage.set("cachedData", JSON.stringify(newData)); // 刷新所有 Widget Widget.reloadAll(); } }); // 2. 注册带参数的 Toggle Intent export const ToggleDoorIntent = AppIntentManager.register<{ id: string; newState: boolean; }>({ name: "ToggleDoorIntent", protocol: AppIntentProtocol.AppIntent, perform: async ({ id, newState }) => { // 调用智能家居 API 切换门状态 await fetch(`https://api.smarthome.com/doors/${id}`, { method: "POST", body: JSON.stringify({ open: newState }), headers: { "Content-Type": "application/json" } }); // 更新本地状态 await Storage.set(`door_${id}_state`, newState.toString()); // 刷新 Control Widget 的 Toggle 状态 ControlWidget.reloadToggles(); } }); // 3. 注册音频播放 Intent(用于媒体控制) export const PlayPauseIntent = AppIntentManager.register<{ action: "play" | "pause"; }>({ name: "PlayPauseIntent", protocol: AppIntentProtocol.AudioPlaybackIntent, // 音频播放协议 perform: async ({ action }) => { if (action === "play") { await AudioPlayer.play(); } else { await AudioPlayer.pause(); } // 更新 Live Activity await LiveActivity.update({ activityId: "music_player", content: { isPlaying: action === "play" } }); } }); // 4. 注册音频录制 Intent(iOS 18+,需要 Live Activity) export const RecordAudioIntent = AppIntentManager.register<{ action: "start" | "stop"; }>({ name: "RecordAudioIntent", protocol: AppIntentProtocol.AudioRecordingIntent, // 音频录制协议 perform: async ({ action }) => { if (action === "start") { // 必须先启动 Live Activity await LiveActivity.start({ activityId: "audio_recording", content: { isRecording: true, duration: 0 } }); await AudioRecorder.startRecording(); } else { await AudioRecorder.stopRecording(); // 结束 Live Activity await LiveActivity.end({ activityId: "audio_recording" }); } } }); // 5. 在 Widget 中使用 AppIntent (widget.tsx) Widget.present( 智能家居控制 {/* 简单按钮 */}