mirror of
https://github.com/kuaifan/dootask.git
synced 2026-06-12 10:22:20 +00:00
Compare commits
No commits in common. "pro" and "v1.7.67" have entirely different histories.
@ -1,119 +0,0 @@
|
||||
---
|
||||
description: 备份 DooTask 数据:数据库(必须)+ public/uploads(排除 tmp,可选)+ docker/appstore/config(可选)。汇总到临时目录并附 README 说明,打包到 backup/ 按日期命名。只读取源数据、绝不删改,失败即停。
|
||||
---
|
||||
|
||||
# DooTask 数据备份
|
||||
|
||||
**刚性技能**——前置检查 → 选可选项 → 确认 → 执行 → 报告。只读取源数据生成归档,**绝不删除或修改任何源数据/既有备份**。任何一步失败立即停止。
|
||||
|
||||
## 备份范围
|
||||
|
||||
| 项 | 来源 | 是否必须 | 说明 |
|
||||
|----|------|---------|------|
|
||||
| 数据库 | `./cmd mysql backup` 产出的 `.sql.gz` | **必须** | 脚本内部用 mysqldump 导出当前库 |
|
||||
| 上传文件 | `public/uploads`(**排除 `public/uploads/tmp`**) | 可选 | 头像/聊天/任务/文件等真实上传数据;`tmp` 是临时目录,可重建,不备份 |
|
||||
| 应用配置 | `docker/appstore/config` | 可选 | 应用市场各应用的配置;含 **root 属主子目录**,收集时可能需 sudo |
|
||||
|
||||
> `docker/appstore/apps` **不在备份范围**——可从应用市场重新安装,无需备份。
|
||||
|
||||
## 前置检查(全部通过才能继续)
|
||||
|
||||
1. **工作目录**:在项目根(存在 `cmd`、`docker-compose.yml`)
|
||||
2. **数据库容器**:`mariadb` 容器在跑(DB 备份依赖它;不在则提示用户先 `./cmd up` 起服务)
|
||||
3. **磁盘空间**:确认 `backup/` 所在盘空间足够(数据库 dump 可能较大)
|
||||
4. **选可选项**:询问用户本次是否包含 `public/uploads` 和 `docker/appstore/config`(**默认两个都含**)
|
||||
|
||||
检查通过、可选项确定后,汇报本次将备份哪些项,**向用户确认一次**再执行。
|
||||
|
||||
## 执行
|
||||
|
||||
用一个统一时间戳贯穿全程:`TS=$(date +%Y%m%d_%H%M%S)`,临时目录 `WORK="tmp/dootask-backup-${TS}"`。
|
||||
|
||||
### 1) 建临时工作目录
|
||||
```shell
|
||||
mkdir -p "$WORK"
|
||||
```
|
||||
(`tmp/` 已被 gitignore,安全)
|
||||
|
||||
### 2) 数据库(必须)
|
||||
```shell
|
||||
./cmd mysql backup
|
||||
```
|
||||
脚本会把 dump 写到 `docker/mysql/backup/<库名>_<时间戳>.sql.gz` 并打印「备份文件:...」。**取该次产出的最新 dump** 复制进工作目录(不用关心它原始落在哪):
|
||||
```shell
|
||||
DB_FILE=$(ls -t docker/mysql/backup/*.sql.gz | head -1)
|
||||
cp "$DB_FILE" "$WORK/"
|
||||
```
|
||||
|
||||
### 3) public/uploads(可选,排除 tmp)
|
||||
```shell
|
||||
rsync -a --exclude='tmp' public/uploads/ "$WORK/uploads/"
|
||||
```
|
||||
> 无 rsync 时用 tar 管道:`mkdir -p "$WORK/uploads" && tar cf - --exclude='./tmp' -C public/uploads . | tar xf - -C "$WORK/uploads"`
|
||||
|
||||
### 4) docker/appstore/config(可选)
|
||||
```shell
|
||||
cp -a docker/appstore/config "$WORK/appstore-config"
|
||||
```
|
||||
> 含 root 属主子目录,若报 `permission denied`:改用 `sudo cp -a ...`,随后把整个工作目录属主归还当前用户,保证后续打包/清理不受阻:
|
||||
> ```shell
|
||||
> sudo chown -R "$(id -u):$(id -g)" "$WORK"
|
||||
> ```
|
||||
|
||||
### 5) 写 README.md(备份说明)
|
||||
在 `$WORK/README.md` 写明本次备份信息,便于日后识别与还原。模板:
|
||||
```markdown
|
||||
# DooTask 备份 — <TS>
|
||||
|
||||
- 备份时间:<人类可读时间>
|
||||
- DooTask 版本:<取自 package.json 的 version>
|
||||
- 包含内容:
|
||||
- 数据库:<DB dump 文件名>(来源 mysqldump 当前库)
|
||||
- 上传文件:uploads/(来源 public/uploads,已排除 tmp) ← 未选则写「未包含」
|
||||
- 应用配置:appstore-config/(来源 docker/appstore/config) ← 未选则写「未包含」
|
||||
- 各项大小:<du -sh 列出工作目录内各项>
|
||||
|
||||
## 还原提示
|
||||
- 数据库:`gunzip < <db>.sql.gz | mysql -u<user> -p<pass> <库名>`,或用 `./cmd mysql recovery` 选对应文件还原。
|
||||
- 上传文件:将 uploads/ 内容覆盖回项目 public/uploads/。
|
||||
- 应用配置:将 appstore-config/ 覆盖回 docker/appstore/config/。
|
||||
```
|
||||
|
||||
### 6) 打包到 backup/,清理临时目录
|
||||
```shell
|
||||
mkdir -p backup
|
||||
tar czf "backup/dootask_backup_${TS}.tar.gz" -C tmp "dootask-backup-${TS}"
|
||||
rm -rf "$WORK"
|
||||
```
|
||||
|
||||
## 报告
|
||||
|
||||
向用户报告:
|
||||
- 最终归档路径:`backup/dootask_backup_<TS>.tar.gz`
|
||||
- 归档大小(`ls -lh`)
|
||||
- 实际包含了哪些项(数据库 + 视选择含/不含 uploads、appstore-config)
|
||||
|
||||
## 失败处理
|
||||
|
||||
- 任何步骤失败立即停止,原样报告错误
|
||||
- **不要**自动重试、不要静默跳过某一项(可选项是否包含由前置确认决定,不在执行中临时变更)
|
||||
- DB 备份失败(如 mariadb 未运行)→ 停止,提示用户起服务后重试
|
||||
- 打包前若工作目录有 root 属主残留导致 tar/rm 失败 → `sudo chown` 归还属主后继续,不要删源数据
|
||||
|
||||
## 禁止项
|
||||
|
||||
| 错误做法 | 正确做法 |
|
||||
|---------|---------|
|
||||
| 为"省空间"删除源数据或既有备份 | 只读取源数据生成归档,源数据一律不动 |
|
||||
| 备份 `public/uploads/tmp` | 排除 tmp(临时、可重建) |
|
||||
| 把 `docker/appstore/apps` 也打进去 | 不在范围,可从应用市场重装 |
|
||||
| 遇 config 的 root 子目录就跳过该项 | `sudo` 收集后 chown 归还,完整备份 |
|
||||
| 不写 README 直接打包 | 每个归档自带 README,便于日后识别还原 |
|
||||
| 把归档写进 git | 归档放 `backup/`(已 gitignore),不提交 |
|
||||
|
||||
## Red Flags —— 出现这些念头立即停下
|
||||
|
||||
- "源数据太大,删点旧的再备份" → 不,备份只读不删
|
||||
- "config 有 root 目录,跳过算了" → 不,sudo 收集后归还属主
|
||||
- "apps 也一起备了更全" → 不,apps 不在范围
|
||||
- "tmp 里临时文件顺手也备了" → 不,明确排除 `public/uploads/tmp`
|
||||
@ -1,76 +0,0 @@
|
||||
---
|
||||
name: dootask-fix-permission
|
||||
description: 修复 DooTask 可写目录(bootstrap/cache、docker、public、storage)的属主/权限:chown 回当前用户 + 目录 chmod 775,对齐 install 的赋权逻辑,赋权不删数据。
|
||||
---
|
||||
|
||||
# DooTask 目录权限修复
|
||||
|
||||
容器内进程常以 **root** 写入挂载目录(`storage`、`public/uploads`、`bootstrap/cache` 等),导致宿主机当前用户对这些文件**没有写权限**,进而触发:
|
||||
|
||||
- `./cmd install` 报「目录【xxx】权限不足」/ 目录权限检测失败
|
||||
- `./cmd build`(vite)报 `EACCES: permission denied, copyfile`(复制 `public/uploads/...` 时)
|
||||
- Laravel 运行时写 `storage`/`bootstrap/cache` 失败
|
||||
|
||||
本技能**对齐 `./cmd install` 的目录赋权逻辑**:对四个可写目录做 `chmod 775`(目录)+ `chown` 回当前用户。
|
||||
|
||||
## 适用目录
|
||||
|
||||
与 install 一致的四个:
|
||||
|
||||
```
|
||||
bootstrap/cache
|
||||
docker
|
||||
public # 含 public/uploads(真实上传数据)
|
||||
storage
|
||||
```
|
||||
|
||||
## 核心原则:赋权,不删数据
|
||||
|
||||
`public/uploads` 含真实上传文件(头像、附件等)。**永远优先 `chown` 改属主,不要删数据。** 即便用户说"清理一下",也只允许清临时目录 `public/uploads/tmp`,**切勿**删 uploads 下其他内容。
|
||||
|
||||
## 前置检查
|
||||
|
||||
1. **工作目录**:在项目根(存在 `cmd` 且这四个目录在)
|
||||
2. **sudo**:改属主需 root(当前文件多为 root 属主)。本机一般可免密 sudo;不行则经 docker 以 root 改权限
|
||||
3. 确认要修的范围:默认四个目录全修;若用户只想解 build 报错,也可只针对 `public`(含 `public/uploads`)
|
||||
|
||||
检查通过后汇报将执行的命令,**向用户确认一次**再执行。
|
||||
|
||||
## 执行
|
||||
|
||||
确认后执行(属主修回当前用户,目录权限 775):
|
||||
|
||||
```shell
|
||||
# 1) 属主修回当前用户(递归)
|
||||
sudo chown -R "$(id -u):$(id -g)" bootstrap/cache docker public storage
|
||||
|
||||
# 2) 目录权限 775(仅目录,对齐 install 的 `find -type d -exec chmod 775`)
|
||||
find bootstrap/cache docker public storage -type d -exec chmod 775 {} \;
|
||||
```
|
||||
|
||||
> 只想解 build 的 uploads 报错时,可只对 `public`:
|
||||
> ```shell
|
||||
> sudo chown -R "$(id -u):$(id -g)" public/uploads
|
||||
> ```
|
||||
|
||||
执行后报告:改了哪些目录、属主/权限现状(可 `ls -ld` 抽查),并提示用户可重试之前失败的 install/build/update。
|
||||
|
||||
## 失败处理
|
||||
|
||||
- `chown` 报权限不足 → 当前用户无 sudo 权限,提示用户用有 root 权限的账户,或经 docker 以 root 执行;不要静默跳过
|
||||
- 任何步骤失败立即停止报告,不自动重试
|
||||
|
||||
## 禁止项
|
||||
|
||||
| 错误做法 | 正确做法 |
|
||||
|---------|---------|
|
||||
| build 报 uploads EACCES 就 `rm` 删文件 | `chown` 修属主,保留数据 |
|
||||
| 删整个 `public/uploads` 清场 | 最多清 `public/uploads/tmp`,别碰真实上传数据 |
|
||||
| 对文件无差别 `chmod 777` | 目录 `chmod 775` + `chown` 回当前用户即可 |
|
||||
| 不加 sudo 直接 chown root 文件 | 改属主需 root |
|
||||
|
||||
## Red Flags —— 出现这些念头立即停下
|
||||
|
||||
- "uploads 复制失败,删掉再 build" → 不,`chown` 赋权,不丢数据
|
||||
- "777 一把梭最省事" → 不,按 install 的 775(目录)+ chown
|
||||
- "权限不够就跳过这个目录" → 不,报告交用户处理 sudo
|
||||
@ -1,74 +0,0 @@
|
||||
---
|
||||
name: dootask-install
|
||||
description: 首次部署 DooTask:前置检查后执行 `sudo ./cmd install`(建库 + migrate --seed 的重操作),刚性流程、单次确认、失败即停。
|
||||
---
|
||||
|
||||
# DooTask 安装流程
|
||||
|
||||
**刚性技能**——前置检查 → 向用户确认一次 → 执行 → 报告结果。任何一步失败立即停止。
|
||||
|
||||
## 核心原则
|
||||
|
||||
**违反字面规则 = 违反流程精神。** 不要擅自增加、省略、合并步骤,不要为"省事"绕过 sudo 或确认。
|
||||
|
||||
`./cmd install` 已把整套安装封装为单条命令(赋权→起容器→`composer install`→`key:generate`→`migrate --seed`→`up -d`)。本技能的职责是**安装前把关、选对参数、执行前确认、已知失败处理**,而不是把脚本逻辑拆开重做。
|
||||
|
||||
## 前置检查(全部通过才能继续)
|
||||
|
||||
执行前依次确认:
|
||||
|
||||
1. **工作目录**:必须在项目根(存在 `cmd`、`docker-compose.yml`、`.env.docker`)
|
||||
2. **Docker**:`docker` 与 `docker-compose`/`docker compose`(v2+) 可用且 daemon 在跑(脚本 `check_docker` 也会查,但提前确认能更早报错)
|
||||
3. **Node.js ≥ 20**(脚本 `check_node` 会查)
|
||||
4. **APP_ID 不冲突**:若 `.env` 已有 `APP_ID` 且被其他实例占用,脚本 `check_instance` 会报错——此时**停止**,提示用户先清空 `.env` 里的 `APP_ID` 和 `APP_IPPR` 再装
|
||||
5. **sudo**:`./cmd install` 需 root(`check_sudo`),用 `sudo ./cmd install` 执行
|
||||
|
||||
⚠️ **这是重操作**:会创建数据库并执行 `migrate --seed`(灌入种子数据)。在已有数据的环境上重装前务必和用户确认,避免覆盖。
|
||||
|
||||
检查通过后汇报结果,**向用户确认一次**再执行。
|
||||
|
||||
## 参数选择
|
||||
|
||||
| 参数 | 作用 | 何时用 |
|
||||
|------|------|--------|
|
||||
| `--port <端口>` | 指定 HTTP 端口(脚本会做端口占用检测) | 用户要自定义端口,或默认端口被占 |
|
||||
| `--relock` | 删除 `node_modules`/`package-lock.json`/`vendor`/`composer.lock` 后重装 | **谨慎**:仅在依赖锁损坏、用户明确要求重建锁时用,会拖慢安装 |
|
||||
|
||||
不确定时不要自作主张加参数,按需询问用户。
|
||||
|
||||
## 执行
|
||||
|
||||
确认后执行(按用户选择带上参数):
|
||||
|
||||
```shell
|
||||
sudo ./cmd install
|
||||
# 或: sudo ./cmd install --port 8080
|
||||
```
|
||||
|
||||
成功后脚本会输出访问地址并调用 `repassword.sh`。执行完向用户报告:访问地址(`http://127.0.0.1:<APP_PORT>`)、以及数据库密码提示。
|
||||
|
||||
## 失败处理
|
||||
|
||||
- 任何步骤失败立即停止,原样报告错误信息
|
||||
- **不要**自动重试,**不要**自动跳过
|
||||
- 常见失败与对应处理:
|
||||
- `APP_ID(xxx)已被其他实例使用` → 停止,让用户清空 `.env` 的 `APP_ID`/`APP_IPPR` 再装
|
||||
- `端口 xxx 已被占用` → 停止,让用户换 `--port`
|
||||
- `目录【xxx】权限不足` / 目录权限检测失败 → 这是目录属主/权限问题,引导用户用 **dootask-fix-permission** 技能修复后重装
|
||||
- `安装依赖失败`(composer)→ 报告,交用户决定(常因网络/镜像源)
|
||||
|
||||
## 禁止项
|
||||
|
||||
| 错误做法 | 正确做法 |
|
||||
|---------|---------|
|
||||
| 不加 sudo 直接 `./cmd install` | 用 `sudo ./cmd install`(脚本强制 root) |
|
||||
| 失败后"我再试一次"或自动跳过 | 立即停止,交还用户 |
|
||||
| 在已有数据环境上不问就重装 | 先确认会 `migrate --seed`,可能影响现有数据 |
|
||||
| 遇权限报错自己乱 `chmod`/`chown` | 走 dootask-fix-permission 技能统一处理 |
|
||||
| 不问就加 `--relock` | 默认不加;仅用户明确要求或锁损坏时用 |
|
||||
|
||||
## Red Flags —— 出现这些念头立即停下
|
||||
|
||||
- "端口/权限报错了我顺手帮 TA 改一下别的" → 停下,只处理本次报的问题,按指引走对应技能
|
||||
- "种子数据应该没事,直接重装" → 不,先确认是否会覆盖现有数据
|
||||
- "sudo 麻烦,先试试不加" → 不,install 必须 root
|
||||
@ -1,204 +0,0 @@
|
||||
---
|
||||
name: dootask-release
|
||||
description: 从 `pro` 分支发布 DooTask 前端新版本:翻译 → 版本号/更新日志 → 构建 → 提交推送,刚性顺序、每步确认、失败即停。
|
||||
---
|
||||
|
||||
# DooTask 发布流程
|
||||
|
||||
**刚性技能**——严格按顺序执行,每步向用户确认,任何一步失败立即停止。
|
||||
|
||||
## 核心原则
|
||||
|
||||
按固定顺序执行,不增删、合并或重排步骤。翻译(Step 1)和更新日志(Step 2)由你直接产出;脚本只做确定性机械工作(算版本号、检测差异、字节级生成语言文件)。
|
||||
|
||||
## 前置检查(全部通过才能继续)
|
||||
|
||||
执行任何发布步骤前,依次检查:
|
||||
|
||||
1. **分支**:必须是 `pro`,否则停止,提示用户切换
|
||||
2. **工作区**:`git status` 必须干净(无未提交变更、无未跟踪文件),否则**停止**并交由用户处理
|
||||
3. **Node.js**:`node --version` 必须 ≥ 20
|
||||
4. **PHP**:`php --version` 必须可用(Step 1 的脚本依赖本地 php,无需容器)。若 host 无 php,停止并提示用户
|
||||
|
||||
检查通过后汇报结果,用户确认后再开始执行。
|
||||
|
||||
## 发布步骤
|
||||
|
||||
**每步执行前**向用户确认;**每步执行后**报告结果。
|
||||
|
||||
开始前先把这份清单复制到你的回复里,逐项勾选、跟踪进度:
|
||||
|
||||
```
|
||||
发布进度:
|
||||
- [ ] 前置检查(分支 pro / 工作区干净 / node≥20 / php 可用)
|
||||
- [ ] Step 1 翻译(diff → 翻译 → apply → generate)
|
||||
- [ ] Step 2 版本号 + CHANGELOG
|
||||
- [ ] Step 3 构建(./cmd prod)
|
||||
- [ ] 汇总变更 → 用户确认 → commit + push
|
||||
- [ ] 确认 GitHub Actions Publish 工作流 success
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 1: 翻译
|
||||
|
||||
多语言数据流:`language/original-{web,api}.txt`(原文/简体中文)→ 经翻译写入 `language/translate.json`(含 9 种语言)→ 生成 `public/language/{web,api}/*`。
|
||||
|
||||
**1.1 检测差异**
|
||||
|
||||
```shell
|
||||
php .claude/skills/dootask-release/scripts/language.php diff
|
||||
```
|
||||
|
||||
输出 JSON:
|
||||
- `regexErrorCount > 0`:translate.json **已有条目**的占位符与某语言值不一致 → **停止**,报告 `regexErrors`,交用户修复(这是历史数据问题,不要自行猜测修改)
|
||||
- `redundantCount > 0`:translate.json 里有、但原文已删除的条目 → 仅作提示(apply 时会自动剔除,不致命)
|
||||
- `needsCount == 0`:无新文案 → **跳到 1.4 直接生成**
|
||||
- `needsCount > 0`:`needs` 数组即待翻译清单,每项 `key` 已转成占位符形式(如 `(%T1)`)→ 进入 1.2
|
||||
|
||||
**1.2 翻译**
|
||||
|
||||
对 `needs` 里的每个 `key`,翻成 8 种语言(`zh` 留空、`key` 原样保留):`zh-CHT` `en` `ko` `ja` `de` `fr` `id` `ru`。
|
||||
|
||||
要求:贴合「项目任务管理系统」语境;占位符 `(%T1)`/`(%M1)` 等原样保留、不可增删改,位置可随目标语言语序调整:
|
||||
|
||||
| 原文 | 翻成英语 |
|
||||
|---|---|
|
||||
| (%T1)的周报[(%T2)][(%T3)月第(%T4)周] | Weekly report of (%T1) [(%T2)] [Week (%T4) of month (%T3)] |
|
||||
| (%T1)提交的「(%M2)」待你审批 | '(%M2)' submitted by (%T1) is waiting for your approval |
|
||||
|
||||
把结果写成一个 JSON 数组文件(建议放 `/tmp/dootask-release-translated.json`,避免污染工作区),每个元素含全部 10 个字段,顺序为:
|
||||
`key, zh, zh-CHT, en, ko, ja, de, fr, id, ru`(`zh` 写 `""`)。
|
||||
|
||||
```json
|
||||
[
|
||||
{"key":"...(%T1)...","zh":"","zh-CHT":"...","en":"...","ko":"...","ja":"...","de":"...","fr":"...","id":"...","ru":"..."}
|
||||
]
|
||||
```
|
||||
|
||||
**1.3 合并进 translate.json**
|
||||
|
||||
```shell
|
||||
php .claude/skills/dootask-release/scripts/language.php apply /tmp/dootask-release-translated.json
|
||||
```
|
||||
|
||||
脚本会校验字段完整性与占位符完整性、追加新条目、剔除冗余项,并按项目原生格式写回 `translate.json`。任一条不合格会报错停止,按提示修正翻译后重试。
|
||||
|
||||
**1.4 生成前端/后端语言文件**
|
||||
|
||||
```shell
|
||||
php .claude/skills/dootask-release/scripts/language.php generate
|
||||
```
|
||||
|
||||
由 `translate.json` 字节级重新生成 `public/language/web/*.js` 与 `public/language/api/*.json`(排序/转义与项目原生工具完全一致,正常情况下 diff 只包含本次新增条目)。
|
||||
|
||||
**1.5 报告**:用 `git status --short language public/language` 汇总本步改动,向用户报告新增了多少条翻译。
|
||||
|
||||
---
|
||||
|
||||
### Step 2: 版本号 + 更新日志
|
||||
|
||||
**2.1 计算并写入版本号**
|
||||
|
||||
```shell
|
||||
node .claude/skills/dootask-release/scripts/version_bump.js
|
||||
```
|
||||
|
||||
脚本据 git 历史算出新 `version` 与 `codeVerson` 并写入 `package.json`,输出 JSON 含:`version`、`prevVersion`、`changelogRange`(如 `<上次release提交>..HEAD`,用于下一步圈定本次更新范围)。
|
||||
|
||||
**2.2 撰写 CHANGELOG**
|
||||
|
||||
读取本次区间的提交:
|
||||
|
||||
```shell
|
||||
git log <changelogRange> --stat
|
||||
```
|
||||
|
||||
`--stat` 会带上每个提交的完整描述正文 + 改动文件清单;光看标题不够时用 `git show <hash>` 看具体代码改动。
|
||||
|
||||
按 `CHANGELOG.md` 现有格式,在文件顶部 `# Changelog` 说明段之后、紧挨上一个 `## [...]` 之前,插入新版本区段:
|
||||
|
||||
```markdown
|
||||
## [<version>]
|
||||
|
||||
### Features
|
||||
|
||||
- ...
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- ...
|
||||
|
||||
### Performance
|
||||
|
||||
- ...
|
||||
```
|
||||
|
||||
撰写要求(对齐项目历史风格):
|
||||
- 小节标题用**英文 Title Case**:`Features` / `Bug Fixes` / `Performance` / `Documentation` / `Security` / `Miscellaneous`,**不要译成中文**;**没有内容的小节整段省略**。
|
||||
- 条目正文用**通俗友好的简体中文**,面向**普通用户**描述更新带来的直接好处,**避免技术术语**(如 refactor、merge branch、commit lint、bump deps 等)。
|
||||
- 过滤掉对用户无意义的提交(纯构建/依赖/CI/合并提交、本技能自身的脚手架改动等)。
|
||||
- 仅凭提交标题无法判断是否对用户有价值时,结合提交的完整描述正文和实际代码改动(`git show <hash>`)再决定,不要只看一行就下结论。
|
||||
- 合并相似项;每个小节内**按用户价值与影响范围排序,重要的在前**。
|
||||
|
||||
**2.3 报告**:展示新版本号与你写的 changelog 区段,请用户过目。
|
||||
|
||||
---
|
||||
|
||||
### Step 3: 构建前端
|
||||
|
||||
```shell
|
||||
./cmd prod
|
||||
```
|
||||
|
||||
构建前端生产版本。用 `./cmd prod`,不要换成裸跑 vite(它还负责 node 检查、清 `public/js/build`、debug 切换)。
|
||||
|
||||
> **已知失败**:build 报 `public/uploads/...` 的 `EACCES: permission denied, copyfile`,是 vite 复制 `public/` 时撞到 root 属主的运行时上传文件(不限于 `tmp`,`avatar` 等都可能)。补救是赋权、不是删数据——把 uploads 属主改回当前用户后重试:
|
||||
> ```shell
|
||||
> sudo chown -R "$(id -u):$(id -g)" public/uploads
|
||||
> ```
|
||||
> `public/uploads` 是真实上传数据,**不要删**;即便要清也只清 `public/uploads/tmp`。
|
||||
|
||||
---
|
||||
|
||||
## 最终:提交并推送
|
||||
|
||||
所有步骤完成后:
|
||||
|
||||
1. 通过 `git diff` + `git status` 汇总所有变更,向用户报告摘要
|
||||
2. **询问用户是否提交并推送**
|
||||
3. 用户明确确认后才执行 `git add`、`git commit`、`git push`
|
||||
4. 未确认一律不执行
|
||||
|
||||
提交规范:
|
||||
- 提交信息使用 `release: v<新版本号>`(与历史一致,参见 `git log --oneline | grep '^release:'`)
|
||||
- **只 add 本次发布相关改动**,按文件名/目录显式添加(例如 `git add package.json CHANGELOG.md language/translate.json public/language public/js`),不要用 `git add -A` / `git add .`,以免卷入未跟踪的本地实验文件
|
||||
- 不打 git tag(现行发布流程不使用 tag)
|
||||
- 确认前先核对:`/tmp/dootask-release-translated.json` 等临时文件不在仓库内,工作区不应残留发布无关的未跟踪文件
|
||||
|
||||
## push 之后:确认发布工作流(CI 才是真正出包)
|
||||
|
||||
push 到 `pro` 只是触发器,真正的构建/出包由 GitHub Actions 完成——**push 成功 ≠ 发布完成**:
|
||||
|
||||
- **Publish**(`.github/workflows/publish.yml`,push→pro 触发)跑完才算出包;成功后会自动触发 **Sync to Gitee**(镜像同步)。
|
||||
- push 完成后**主动确认** Publish 工作流 `conclusion=success`。优先用 `gh`(未装可临时装;公开仓库也可用 GitHub REST API 免鉴权读取 runs):
|
||||
```shell
|
||||
gh run list --workflow=publish.yml -R kuaifan/dootask -L 1
|
||||
gh run view <run-id> -R kuaifan/dootask --json status,conclusion,url
|
||||
```
|
||||
- 工作流仍在跑时,挂后台轮询、结束即通知用户,**不要在前台死等**。
|
||||
|
||||
### iOS 发布(询问后决定)
|
||||
|
||||
`ios-publish.yml` 是**独立的手动工作流**(`workflow_dispatch`),不随 push 触发。Publish 成功后,用 options 或 AskUserQuestion 形式提问是否同时发布 iOS(选项:发布 iOS / 不发布):
|
||||
|
||||
- 选「发布 iOS」才执行:
|
||||
```shell
|
||||
gh workflow run ios-publish.yml --ref pro -R kuaifan/dootask
|
||||
```
|
||||
需 `gh` 已登录且 token 含 `workflow` 权限;触发后可挂后台轮询结果。
|
||||
- 选「不发布」则结束。
|
||||
|
||||
## 失败处理
|
||||
|
||||
任何步骤失败立即停止、报告错误信息,交用户决定;不要自动重试或跳过。
|
||||
@ -1,239 +0,0 @@
|
||||
<?php
|
||||
// DooTask 发布——翻译流水线(纯本地 php,host 直接跑,不进容器、不调 OpenAI、不需 autoload)。
|
||||
// 逐行对齐 language/translate.php 的检测/保存/生成逻辑,唯独把"调用外部模型翻译"那一段抽走,
|
||||
// 翻译改在技能流程内完成。用 php 而非 node 的唯一原因:array_multisort + json_encode
|
||||
// 的逐字节产物必须与项目原生工具一致,否则每次发版都会产生大面积排序/转义噪声 diff(已验证 host php 可字节级复现)。
|
||||
//
|
||||
// 子命令:
|
||||
// language.php diff
|
||||
// —— 输出 JSON:needs(待翻译,key 已转成 (%T1)/(%M1) 形式) / redundants(冗余,提示) / regexErrors(占位符错乱,致命)
|
||||
// language.php apply <translated.json>
|
||||
// —— 把新翻译合并进 translate.json(追加 + 剔除冗余),不生成 public 文件
|
||||
// language.php generate
|
||||
// —— 由 translate.json 重新生成 public/language/{web,api}/*
|
||||
//
|
||||
// 项目根相对脚本自身定位(脚本固定在 <root>/.claude/skills/dootask-release/scripts/),与调用时的 cwd 无关。
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
$ROOT = dirname(__DIR__, 4);
|
||||
$LANG_DIR = $ROOT . '/language';
|
||||
$LANG_FIELDS = ['key', 'zh', 'zh-CHT', 'en', 'ko', 'ja', 'de', 'fr', 'id', 'ru'];
|
||||
|
||||
if (!is_dir($LANG_DIR)) {
|
||||
fwrite(STDERR, "未找到 language 目录($LANG_DIR)。\n");
|
||||
exit(1);
|
||||
}
|
||||
chdir($LANG_DIR);
|
||||
|
||||
$cmd = $argv[1] ?? '';
|
||||
|
||||
// ---- 公共:读取 original-*.txt ----
|
||||
function read_generateds(): array
|
||||
{
|
||||
$originals = [];
|
||||
$generateds = [];
|
||||
foreach (['web', 'api'] as $type) {
|
||||
$content = file_exists("original-{$type}.txt") ? file_get_contents("original-{$type}.txt") : "";
|
||||
$array = array_values(array_filter(array_unique(explode("\n", $content))));
|
||||
$generateds[$type] = $array;
|
||||
$originals = array_merge($originals, $array);
|
||||
}
|
||||
return [$originals, $generateds];
|
||||
}
|
||||
|
||||
// ---- 公共:构建 translations 映射(normalizedKey -> obj),并收集冗余/占位符错乱 ----
|
||||
function build_translations(array $originals): array
|
||||
{
|
||||
$translations = [];
|
||||
$redundants = [];
|
||||
$regrror = [];
|
||||
if (!file_exists("translate.json")) {
|
||||
fwrite(STDERR, "translate.json not exists\n");
|
||||
exit(1);
|
||||
}
|
||||
$tmps = json_decode(file_get_contents("translate.json"), true);
|
||||
foreach ($tmps as $obj) {
|
||||
if (!isset($obj['key'])) {
|
||||
continue;
|
||||
}
|
||||
$currentKey = $obj['key'];
|
||||
$originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $currentKey);
|
||||
if (!in_array($originalKey, $originals)) {
|
||||
$redundants[$originalKey] = $obj;
|
||||
continue;
|
||||
}
|
||||
$translations[$originalKey] = $obj;
|
||||
if (preg_match_all('/\(%[TM]\d+\)/', $currentKey, $matches)) {
|
||||
foreach ($matches[0] as $match) {
|
||||
foreach ($obj as $k => $v) {
|
||||
if (empty($v)) {
|
||||
continue;
|
||||
}
|
||||
if (!str_contains($v, $match)) {
|
||||
$regrror[$originalKey] = ['key' => $currentKey, 'field' => $k, 'value' => $v, 'match' => $match];
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [$translations, $redundants, $regrror];
|
||||
}
|
||||
|
||||
// ---- 公共:由 translate.json + originals 重新生成 public 文件 ----
|
||||
function generate(array $generateds, array $translations): void
|
||||
{
|
||||
foreach ($generateds as $type => $array) {
|
||||
$datas = [];
|
||||
foreach ($array as $text) {
|
||||
$text = trim($text);
|
||||
if (isset($translations[$text])) {
|
||||
$datas[] = $translations[$text];
|
||||
}
|
||||
}
|
||||
$inOrder = [];
|
||||
foreach ($datas as $index => $item) {
|
||||
if (preg_match('/\(%[TM]\d+\)/', $item['key'])) {
|
||||
$inOrder[$index] = strlen($item['key']);
|
||||
} else {
|
||||
$inOrder[$index] = strlen($item['key']) + 10000000000;
|
||||
}
|
||||
}
|
||||
array_multisort($inOrder, SORT_DESC, $datas);
|
||||
$results = [];
|
||||
foreach ($datas as $items) {
|
||||
foreach ($items as $kk => $item) {
|
||||
$results[$kk][] = $item;
|
||||
}
|
||||
}
|
||||
if ($type === 'api') {
|
||||
if (!is_dir("../public/language/api")) {
|
||||
mkdir("../public/language/api", 0777, true);
|
||||
}
|
||||
foreach ($results as $kk => $item) {
|
||||
file_put_contents("../public/language/api/$kk.json", json_encode($item, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
} elseif ($type === 'web') {
|
||||
if (!is_dir("../public/language/web")) {
|
||||
mkdir("../public/language/web", 0777, true);
|
||||
}
|
||||
foreach ($results as $kk => $item) {
|
||||
file_put_contents("../public/language/web/$kk.js", "if(typeof window.LANGUAGE_DATA===\"undefined\")window.LANGUAGE_DATA={};window.LANGUAGE_DATA[\"{$kk}\"]=" . json_encode($item, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
echo "[$type] total: " . count($results['key']) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ($cmd === 'diff') {
|
||||
[$originals, $generateds] = read_generateds();
|
||||
[$translations, $redundants, $regrror] = build_translations($originals);
|
||||
|
||||
// 需要翻译的数据(对齐 translate.php 150-169:占位符按单一计数器编号)
|
||||
$needs = [];
|
||||
foreach ($originals as $text) {
|
||||
$key = trim($text);
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($translations[$key])) {
|
||||
$needs[$key] = $key;
|
||||
}
|
||||
}
|
||||
$needsOut = [];
|
||||
foreach ($needs as $key) {
|
||||
$c = 1;
|
||||
$converted = preg_replace_callback('/\((\*+)\)/', function ($m) use (&$c) {
|
||||
$label = strlen($m[1]) > 1 ? "M" : "T";
|
||||
return "(%" . $label . $c++ . ")";
|
||||
}, $key);
|
||||
$needsOut[] = ['key' => $converted];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'needsCount' => count($needsOut),
|
||||
'redundantCount' => count($redundants),
|
||||
'regexErrorCount' => count($regrror),
|
||||
'needs' => $needsOut,
|
||||
'redundants' => array_keys($redundants),
|
||||
'regexErrors' => array_values($regrror),
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||
|
||||
if (count($regrror) > 0) {
|
||||
exit(2); // 已有数据占位符错乱,需先修复
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ($cmd === 'apply') {
|
||||
$file = $argv[2] ?? '';
|
||||
if ($file === '' || !file_exists($file)) {
|
||||
fwrite(STDERR, "用法:apply <translated.json>(文件不存在)\n");
|
||||
exit(1);
|
||||
}
|
||||
[$originals, $generateds] = read_generateds();
|
||||
[$translations, $redundants, $regrror] = build_translations($originals);
|
||||
if (count($regrror) > 0) {
|
||||
fwrite(STDERR, "translate.json 已有条目占位符错乱,请先修复再发版。\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
$incoming = json_decode(file_get_contents($file), true);
|
||||
if (!is_array($incoming)) {
|
||||
fwrite(STDERR, "translated.json 必须是数组\n");
|
||||
exit(1);
|
||||
}
|
||||
$added = 0;
|
||||
foreach ($incoming as $raw) {
|
||||
foreach ($GLOBALS['LANG_FIELDS'] as $f) {
|
||||
if (!array_key_exists($f, $raw)) {
|
||||
fwrite(STDERR, "新翻译缺字段 \"$f\":" . json_encode($raw, JSON_UNESCAPED_UNICODE) . "\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// 占位符完整性:key 里每个 (%T1)/(%M1) 必须出现在每个非空语言值里
|
||||
if (preg_match_all('/\(%[TM]\d+\)/', $raw['key'], $m)) {
|
||||
foreach ($m[0] as $match) {
|
||||
foreach ($GLOBALS['LANG_FIELDS'] as $f) {
|
||||
if ($f === 'key' || $f === 'zh') {
|
||||
continue;
|
||||
}
|
||||
if (empty($raw[$f])) {
|
||||
continue;
|
||||
}
|
||||
if (!str_contains($raw[$f], $match)) {
|
||||
fwrite(STDERR, "占位符 $match 在字段 \"$f\" 缺失:{$raw['key']}\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 规范化:固定字段顺序 + zh 置空
|
||||
$item = [];
|
||||
foreach ($GLOBALS['LANG_FIELDS'] as $f) {
|
||||
$item[$f] = $f === 'zh' ? '' : $raw[$f];
|
||||
}
|
||||
$originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $item['key']);
|
||||
$translations[$originalKey] = $item;
|
||||
$added++;
|
||||
}
|
||||
|
||||
// array_values:现有条目(去冗余)在前,新条目追加在后
|
||||
file_put_contents("translate.json", json_encode(array_values($translations), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
echo json_encode([
|
||||
'added' => $added,
|
||||
'total' => count($translations),
|
||||
'droppedRedundant' => count($redundants),
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ($cmd === 'generate') {
|
||||
[$originals, $generateds] = read_generateds();
|
||||
[$translations] = build_translations($originals);
|
||||
generate($generateds, $translations);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
fwrite(STDERR, "未知子命令:'$cmd'。可用:diff | apply <file> | generate\n");
|
||||
exit(1);
|
||||
@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 计算并写入新版本号到 package.json(version + codeVerson),算法对齐 bin/version.js。
|
||||
// 不生成 CHANGELOG(在技能流程内撰写),只输出版本号与 changelog 的提交区间。
|
||||
//
|
||||
// 项目根相对脚本自身定位(脚本固定在 <root>/.claude/skills/dootask-release/scripts/),与调用时的 cwd 无关。
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../../../..');
|
||||
const pkgFile = path.join(ROOT, 'package.json');
|
||||
const verOffset = 6394; // 版本号偏移量(与 bin/version.js 一致)
|
||||
const codeOffset = 35; // 代码版本号偏移量
|
||||
|
||||
function git(cmd) {
|
||||
return execSync(cmd, { cwd: ROOT, maxBuffer: 1024 * 1024 * 10 }).toString().trim();
|
||||
}
|
||||
|
||||
const verCount = parseInt(git('git rev-list --count HEAD'), 10);
|
||||
const codeCount = parseInt(git("git tag --merged pro -l 'v*' | wc -l"), 10);
|
||||
const num = verOffset + verCount;
|
||||
if (Number.isNaN(num)) {
|
||||
console.error(`版本计算失败:rev-list count=${verCount}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const version = `${Math.floor(num / 10000)}.${Math.floor((num % 10000) / 100)}.${Math.floor(num % 100)}`;
|
||||
const codeVersion = codeOffset + codeCount;
|
||||
|
||||
let pkg = fs.readFileSync(pkgFile, 'utf8');
|
||||
const prevVersion = (pkg.match(/"version":\s*"(.*?)"/) || [])[1] || '';
|
||||
pkg = pkg.replace(/"version":\s*"(.*?)"/, `"version": "${version}"`);
|
||||
pkg = pkg.replace(/"codeVerson":(.*?)(,|$)/, `"codeVerson": ${codeVersion}$2`);
|
||||
fs.writeFileSync(pkgFile, pkg, 'utf8');
|
||||
|
||||
// 上一个 release 提交作为 changelog 区间下界
|
||||
let prevReleaseCommit = '';
|
||||
try {
|
||||
prevReleaseCommit = git("git log --grep='^release: v' -n 1 --pretty=format:%H");
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
console.log(JSON.stringify({
|
||||
version,
|
||||
codeVersion,
|
||||
prevVersion,
|
||||
prevReleaseCommit,
|
||||
changelogRange: prevReleaseCommit ? `${prevReleaseCommit}..HEAD` : '(未找到上一个 release 提交,需人工确定区间)',
|
||||
}, null, 2));
|
||||
@ -1,83 +0,0 @@
|
||||
---
|
||||
name: dootask-update
|
||||
description: 更新已部署的 DooTask:前置检查后执行 `sudo ./cmd update`(拉代码 + composer + 迁移 + 重启),本地有改动时停下交用户决定,不自动强制、失败即停。
|
||||
---
|
||||
|
||||
# DooTask 更新流程
|
||||
|
||||
**刚性技能**——前置检查 → 向用户确认一次 → 执行 → 报告结果。任何一步失败立即停止。
|
||||
|
||||
## 核心原则
|
||||
|
||||
**违反字面规则 = 违反流程精神。** 不要擅自加步骤、绕过 sudo/确认,**尤其不要替用户决定强制更新**(会丢本地改动)。
|
||||
|
||||
`./cmd update` 已封装整套更新(检测本地改动→`git fetch`→必要时备份库→`git pull/reset`→`composer install`→`migrate`→重启 php+nginx→写 `UPDATE_TIME`)。本技能职责是**更新前把关、选对参数、处理本地改动这一关键岔路、执行前确认**。
|
||||
|
||||
## 前置检查(全部通过才能继续)
|
||||
|
||||
1. **已安装**:必须存在 `vendor/autoload.php`(脚本会查,没装则报"请先执行安装命令"——此时引导用户走 dootask-install)
|
||||
2. **工作目录**:在项目根
|
||||
3. **当前分支 / 目标分支**:默认更新当前分支;用户要切分支用 `--branch <分支>`。若用户没说,确认是否就更新当前分支
|
||||
4. **本地改动**(关键):`git status` 看是否有未提交改动
|
||||
5. **sudo**:`sudo ./cmd update` 需 root
|
||||
|
||||
检查通过后汇报结果,**向用户确认一次**再执行。
|
||||
|
||||
## 关键岔路:本地有改动
|
||||
|
||||
脚本检测到本地改动时会询问是否强制更新。**强制更新 = `git reset --hard origin/<分支>`,会丢弃所有本地改动。**
|
||||
|
||||
- 发现本地有改动 → **停下**,把改动清单报告用户,让**用户决定**:先提交/暂存改动,还是确认强制更新
|
||||
- **不要**替用户选 `--force`
|
||||
- 只有用户明确说"丢掉改动强制更新"时,才带 `--force`
|
||||
|
||||
## 参数选择
|
||||
|
||||
| 参数 | 作用 | 何时用 |
|
||||
|------|------|--------|
|
||||
| `--branch <分支>` | 切到指定分支再更新 | 用户要换分支(如切 `dev`/`pro`) |
|
||||
| `--force` | 强制更新:`git checkout -f` + `git reset --hard` | **危险**:仅用户明确接受"丢弃本地改动"后 |
|
||||
| `--local` | 本地更新模式:只备份库 + `migrate` + 重启,不拉远程代码 | 代码已就位(如手动改过/CI 拉过),只需迁移+重启 |
|
||||
|
||||
## 数据库
|
||||
|
||||
- 远程模式下,脚本检测到 `database/` 目录有迁移变动会**自动备份数据库**再继续——这是脚本内置的,无需手动。
|
||||
- 但若是大版本升级或用户在意数据,执行前提醒用户:本次可能含库迁移,已有自动备份兜底;如需可先 `./cmd mysql backup` 额外备份。
|
||||
|
||||
## 执行
|
||||
|
||||
确认(含本地改动决策)后执行:
|
||||
|
||||
```shell
|
||||
sudo ./cmd update
|
||||
# 切分支: sudo ./cmd update --branch pro
|
||||
# 强制(丢改动,用户确认后): sudo ./cmd update --force
|
||||
# 本地模式: sudo ./cmd update --local
|
||||
```
|
||||
|
||||
成功后报告:更新到的分支、是否做了库备份/迁移、服务是否重启完成。
|
||||
|
||||
## 失败处理
|
||||
|
||||
- 任何步骤失败立即停止,原样报告错误
|
||||
- **不要**自动重试、不要自动跳过、不要因为 `git pull` 失败就自己改成 `--force`
|
||||
- 常见失败:
|
||||
- `请先执行安装命令` → 走 dootask-install
|
||||
- `代码拉取失败,可能存在冲突` → 报告,让用户决定是否 `--force`(丢改动)或先处理冲突
|
||||
- 重启服务失败 → 脚本会尝试 `down` 后重起;若仍失败,报告交用户
|
||||
|
||||
## 禁止项
|
||||
|
||||
| 错误做法 | 正确做法 |
|
||||
|---------|---------|
|
||||
| 检测到本地改动就自动 `--force` | 停下,报告改动,交用户决定 |
|
||||
| `git pull` 失败就自动改用 `--force` | 报告冲突,交用户 |
|
||||
| 不加 sudo | `sudo ./cmd update` |
|
||||
| 未装就更新 | 先走 dootask-install |
|
||||
| 失败后自动重试/跳过 | 立即停止 |
|
||||
|
||||
## Red Flags —— 出现这些念头立即停下
|
||||
|
||||
- "有点本地改动,强制更新一下就好了" → 不,`--force` 会丢改动,必须用户拍板
|
||||
- "拉取冲突了,我 reset 一下" → 不,交用户决定
|
||||
- "已经装过了吧,直接更新" → 先确认 `vendor/autoload.php` 在
|
||||
83
.claude/skills/release/SKILL.md
Normal file
83
.claude/skills/release/SKILL.md
Normal file
@ -0,0 +1,83 @@
|
||||
---
|
||||
name: release
|
||||
description: Use when releasing a new DooTask frontend version from the `pro` branch. Rigid sequential workflow (translate → version → build → commit → push) with strict pre-checks (branch, clean worktree, Node 20+) and per-step user confirmation. Use when user says "发布新版本", "release", "出新版本", "打版本". Stop on any failure; do NOT auto-fix dirty worktree, do NOT add tag step, do NOT use `git add -A`.
|
||||
---
|
||||
|
||||
# DooTask 发布流程
|
||||
|
||||
**刚性技能**——严格按顺序执行,每步向用户确认,任何一步失败立即停止。
|
||||
|
||||
## 核心原则
|
||||
|
||||
**违反字面规则 = 违反流程精神。** 不要擅自增加、省略、合并或重排步骤。
|
||||
|
||||
## 前置检查(全部通过才能继续)
|
||||
|
||||
执行任何发布步骤前,依次检查:
|
||||
|
||||
1. **分支**:必须是 `pro`,否则停止,提示用户切换
|
||||
2. **工作区**:`git status` 必须干净(无未提交变更、无未跟踪文件),否则**停止**并交由用户处理
|
||||
3. **Node.js**:必须 ≥ 20,否则停止
|
||||
|
||||
检查通过后汇报结果,用户确认后再开始执行。
|
||||
|
||||
## 发布步骤
|
||||
|
||||
**每步执行前**向用户确认;**每步执行后**报告结果。
|
||||
|
||||
### Step 1: 翻译
|
||||
```shell
|
||||
npm run translate
|
||||
```
|
||||
更新多语言翻译文件。
|
||||
|
||||
### Step 2: 版本号
|
||||
```shell
|
||||
npm run version
|
||||
```
|
||||
更新版本号。
|
||||
|
||||
### Step 3: 构建前端
|
||||
```shell
|
||||
npm run build
|
||||
```
|
||||
构建前端生产版本。
|
||||
|
||||
## 最终:提交并推送
|
||||
|
||||
所有步骤完成后:
|
||||
|
||||
1. 通过 `git diff` + `git status` 汇总所有变更,向用户报告摘要
|
||||
2. **询问用户是否提交并推送**
|
||||
3. 用户明确确认后才执行 `git add`、`git commit`、`git push`
|
||||
4. 未确认一律不执行
|
||||
|
||||
提交规范:
|
||||
- 提交信息使用 `release: v<新版本号>`(与历史提交风格一致,参见 `git log --oneline | grep '^release:'`)
|
||||
- **只 add 本次发布相关改动**,按文件名显式添加(例如 `git add package.json public/js/...`),**不要用 `git add -A` 或 `git add .`**,以免卷入未跟踪的本地实验文件
|
||||
|
||||
## 失败处理
|
||||
|
||||
- 任何步骤失败立即停止,报告错误信息
|
||||
- **不要**自动重试
|
||||
- **不要**自动跳过失败步骤
|
||||
- 由用户决定如何处理
|
||||
|
||||
## 禁止项(基线测试暴露的反模式)
|
||||
|
||||
| 错误做法 | 正确做法 |
|
||||
|---------|---------|
|
||||
| 遇到脏工作区主动提出修复方案(加 `.gitignore`、先 push 等) | **停下**,报告脏工作区事实,交用户决定 |
|
||||
| 增加 `git tag v1.7.xx` 步骤 | DooTask 现行发布流程**不打 tag**,不要擅自添加 |
|
||||
| `git add -A` / `git add .` | 按文件名显式添加发布相关改动 |
|
||||
| 一次性 add + commit + push,不给确认机会 | 摘要 → 问确认 → 再 add/commit/push 三步分离 |
|
||||
| 把 translate/version/build 顺序自作主张调整 | 顺序固定为 translate → version → build |
|
||||
| 失败后"我再试一次"或"跳过这步" | 立即停止,交还给用户 |
|
||||
|
||||
## Red Flags —— 出现这些念头立即停下
|
||||
|
||||
- "这个脏工作区我来帮 TA 搞定一下" → 停下,交用户
|
||||
- "顺便打个 tag 吧" → 不,没有这一步
|
||||
- "`git add -A` 省事" → 不,显式 add
|
||||
- "翻译这步没改动可以跳" → 不,按顺序执行、执行后报告结果即可
|
||||
- "一起 commit + push 一气呵成" → 必须先让用户确认
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,7 +7,6 @@
|
||||
/public/hot
|
||||
/public/tmp
|
||||
/tmp
|
||||
/backup
|
||||
|
||||
# Uploads and user-generated content
|
||||
/public/summary
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@ -2,30 +2,6 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.7.90]
|
||||
|
||||
### Features
|
||||
|
||||
- 系统设置新增「创建项目」权限开关,可指定由所有人、部门负责人或特定人员创建项目,未授权时自动隐藏新建入口,管理更清晰。
|
||||
- 会员卡片新增「项目与任务」入口,可直接查看该成员参与的项目、待办与已完成任务,团队协作一目了然。
|
||||
- 审批详情支持删除已结束的审批,由发起人或管理员清理无用记录更方便。
|
||||
- 管理员现在可以设置全员群的群名称,便于统一团队群组的展示。
|
||||
|
||||
## [1.7.81]
|
||||
|
||||
### Features
|
||||
|
||||
- 团队管理中可标记成员邮箱认证状态,成员信息更易管理。
|
||||
- 系统管理员可在任意群组中设置或取消他人的待办,协作管理更灵活。
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复 AI 助手消息推送中发送者身份不完整的问题。
|
||||
|
||||
### Performance
|
||||
|
||||
- 优化大文件下载方式,下载更稳定、更高效。
|
||||
|
||||
## [1.7.67]
|
||||
|
||||
### Features
|
||||
|
||||
@ -22,7 +22,6 @@ English | **[中文文档](./README_CN.md)**
|
||||
- Required: `Docker v20.10+` and `Docker Compose v2.0+`
|
||||
- Supported Systems: `CentOS/Debian/Ubuntu/macOS` and other Linux/Unix systems
|
||||
- Hardware Recommendation: 2+ cores, 4GB+ memory
|
||||
- Database: MariaDB (provided by the default Docker Compose `mariadb` service)
|
||||
- Special Note: Windows users can install Linux environment using WSL2 before installing DooTask.
|
||||
|
||||
### Deploy Project
|
||||
@ -116,15 +115,13 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
After installing the new project, follow these steps to complete migration:
|
||||
|
||||
1、Backup the MariaDB database
|
||||
1、Backup original database
|
||||
|
||||
```bash
|
||||
# Run command in the old project
|
||||
./cmd mysql backup
|
||||
```
|
||||
|
||||
> `./cmd mysql` is the CLI subcommand name; backups run against the MariaDB container.
|
||||
|
||||
2、Copy the following files and directories from old project to the same paths in new project
|
||||
|
||||
- `Database backup file`
|
||||
|
||||
@ -22,7 +22,6 @@
|
||||
- 必须安装:`Docker v20.10+` 和 `Docker Compose v2.0+`
|
||||
- 支持环境:`Centos/Debian/Ubuntu/macOS` 等 linux/unix 系统
|
||||
- 硬件建议:2核4G以上
|
||||
- 数据库:MariaDB(默认 Docker Compose 中的 `mariadb` 服务)
|
||||
- 特别说明:Windows 可以使用 WSL2 安装 Linux 环境后再安装 DooTask。
|
||||
|
||||
### 部署项目
|
||||
@ -116,15 +115,13 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
在新项目安装好之后按照以下步骤完成项目迁移:
|
||||
|
||||
1、备份 MariaDB 数据库
|
||||
1、备份原数据库
|
||||
|
||||
```bash
|
||||
# 在旧的项目下执行指令
|
||||
./cmd mysql backup
|
||||
```
|
||||
|
||||
> `./cmd mysql` 为 CLI 子命令名称,实际操作的是 MariaDB 容器。
|
||||
|
||||
2、将旧项目以下文件和目录拷贝至新项目同路径位置
|
||||
|
||||
- `数据库备份文件`
|
||||
|
||||
@ -9,9 +9,9 @@
|
||||
|
||||
## 发布版本
|
||||
|
||||
> 翻译、版本号、更新日志改由 `dootask-release` 技能完成(见 `.claude/skills/dootask-release/`)。
|
||||
|
||||
```shell
|
||||
npm run translate # 翻译(可选)
|
||||
npm run version # 生成版本
|
||||
npm run build # 编译前端
|
||||
```
|
||||
|
||||
|
||||
@ -348,37 +348,6 @@ class ApproveController extends AbstractController
|
||||
return Base::retSuccess('已撤回', Base::arrayKeyToUnderline($task['data']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/approve/process/delById 删除审批(流程实例)
|
||||
*
|
||||
* @apiDescription 需要token身份;仅可删除已结束的审批,且仅发起人或管理员可删
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup approve
|
||||
* @apiName process__delById
|
||||
*
|
||||
* @apiQuery {Number} proc_inst_id 流程实例ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function process__delById()
|
||||
{
|
||||
$user = User::auth();
|
||||
$data['userid'] = (string)$user->userid;
|
||||
$data['proc_inst_id'] = intval(Request::input('proc_inst_id'));
|
||||
$data['is_admin'] = $user->isAdmin();
|
||||
if ($data['proc_inst_id'] <= 0) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
$ret = Ihttp::ihttp_post($this->flow_url . '/api/v1/workflow/process/delById', json_encode(Base::arrayKeyToCamel($data)));
|
||||
$task = json_decode($ret['ret'] == 1 ? $ret['data'] : '{}', true);
|
||||
if (!$task || $task['status'] != 200) {
|
||||
return Base::retError($task['message'] ?? '删除失败');
|
||||
}
|
||||
return Base::retSuccess('已删除');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/approve/process/findTask 查询需要我审批的流程(审批中)
|
||||
*
|
||||
|
||||
@ -1716,7 +1716,6 @@ class DialogController extends AbstractController
|
||||
* @apiParam {String} text 消息内容
|
||||
* @apiParam {String} [text_type=md] 消息格式:md 或 html
|
||||
* @apiParam {String} [silence=no] 是否静默发送:yes/no
|
||||
* @apiParam {String} [nickname] 自定义发送者昵称(最多20字,留空则显示"AI 助手")
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -1731,7 +1730,6 @@ class DialogController extends AbstractController
|
||||
$text = trim(Request::input('text'));
|
||||
$text_type = strtolower(trim(Request::input('text_type'))) ?: 'md';
|
||||
$silence = in_array(strtolower(trim(Request::input('silence'))), ['yes', 'true', '1']);
|
||||
$nickname = trim(Request::input('nickname'));
|
||||
$markdown = in_array($text_type, ['md', 'markdown']);
|
||||
//
|
||||
if (empty($dialog_id) && empty($task_id)) {
|
||||
@ -1743,9 +1741,6 @@ class DialogController extends AbstractController
|
||||
if (mb_strlen($text) > 200000) {
|
||||
return Base::retError('消息内容最大不能超过200000字');
|
||||
}
|
||||
if (mb_strlen($nickname) > 20) {
|
||||
return Base::retError('发送者昵称最多不能超过20字');
|
||||
}
|
||||
//
|
||||
if ($dialog_id) {
|
||||
// Direct dialog mode: verify user is a member
|
||||
@ -1792,9 +1787,6 @@ class DialogController extends AbstractController
|
||||
if ($markdown) {
|
||||
$msgData['type'] = 'md';
|
||||
}
|
||||
if ($nickname !== '') {
|
||||
$msgData['nickname'] = $nickname;
|
||||
}
|
||||
//
|
||||
$result = WebSocketDialogMsg::sendMsg(
|
||||
null,
|
||||
@ -2655,7 +2647,7 @@ class DialogController extends AbstractController
|
||||
if (Base::settingFind('system', 'todo_set_permission') === 'close') {
|
||||
$others = array_diff($userids, [$user->userid]);
|
||||
if ($others && !$dialog->checkTodoOwnerPermission($user->userid)) {
|
||||
return Base::retError('仅群主、项目/任务负责人或系统管理员可设置或取消他人待办');
|
||||
return Base::retError('仅群主、项目/任务负责人可设置或取消他人待办');
|
||||
}
|
||||
}
|
||||
//
|
||||
@ -2916,9 +2908,7 @@ class DialogController extends AbstractController
|
||||
$data['avatar'] = Base::fillUrl($array['avatar'] = $avatar);
|
||||
}
|
||||
$existName = Request::exists('chat_name') || Request::exists('name');
|
||||
// 个人群组群主可改名;全员群仅系统管理员可改名
|
||||
$canEditName = $dialog->group_type === 'user' || ($dialog->group_type === 'all' && $admin === 1);
|
||||
if ($existName && $canEditName) {
|
||||
if ($existName && $dialog->group_type === 'user') {
|
||||
$chatName = trim(Request::input('chat_name') ?: Request::input('name'));
|
||||
if (mb_strlen($chatName) < 2) {
|
||||
return Base::retError('群名称至少2个字');
|
||||
|
||||
@ -1490,214 +1490,6 @@ class ProjectController extends AbstractController
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/user/projects 会员参与的项目列表
|
||||
*
|
||||
* @apiDescription 需要token身份。用于会员卡片查看「该会员参与的项目」。
|
||||
* 权限:本人 / 系统管理员 / 对该会员具有部门负责人只读视角。
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName user__projects
|
||||
*
|
||||
* @apiParam {Number} userid 目标会员ID
|
||||
* @apiParam {String} [archived] 是否归档(all/yes/no),默认no
|
||||
* @apiParam {Object} [keys] 搜索条件(keys.name 项目名称)
|
||||
* @apiParam {Number} [page] 当前页,默认1
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function user__projects()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$targetId = intval(Request::input('userid'));
|
||||
$context = UserDepartment::userWorksContext($viewer, $targetId);
|
||||
if (!$context['allowed']) {
|
||||
return Base::retError('没有查看权限');
|
||||
}
|
||||
$readonly = !$context['is_self'] && !$context['is_admin'];
|
||||
//
|
||||
$archived = Request::input('archived', 'no');
|
||||
$keys = Request::input('keys');
|
||||
//
|
||||
$builder = Project::select(['projects.*', 'project_users.owner', 'project_users.top_at', 'project_users.sort'])
|
||||
->join('project_users', function ($join) use ($targetId) {
|
||||
$join->on('projects.id', '=', 'project_users.project_id')
|
||||
->where('project_users.userid', '=', $targetId);
|
||||
});
|
||||
// 部门负责人视角:限定在允许可见的项目集合内
|
||||
if ($readonly) {
|
||||
$builder->whereIn('projects.id', $context['project_ids'] ?: [0]);
|
||||
}
|
||||
//
|
||||
if ($archived == 'yes') {
|
||||
$builder->whereNotNull('projects.archived_at');
|
||||
} elseif ($archived == 'no') {
|
||||
$builder->whereNull('projects.archived_at');
|
||||
}
|
||||
if (is_array($keys) && !empty($keys['name'])) {
|
||||
$builder->where('projects.name', 'like', "%{$keys['name']}%");
|
||||
}
|
||||
//
|
||||
$list = $builder
|
||||
->orderByDesc('project_users.top_at')
|
||||
->orderBy('project_users.sort')
|
||||
->orderByDesc('projects.id')
|
||||
->paginate(Base::getPaginate(100, 50));
|
||||
$list->transform(function (Project $project) use ($targetId, $readonly) {
|
||||
$array = $project->toArray();
|
||||
$array['department_readonly'] = $readonly;
|
||||
$array = array_merge($array, $project->getTaskStatistics($targetId));
|
||||
return $array;
|
||||
});
|
||||
//
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/user/tasks 会员参与的任务列表
|
||||
*
|
||||
* @apiDescription 需要token身份。用于会员卡片查看「该会员参与的任务」(负责的 / 协作的)。
|
||||
* 权限:本人 / 系统管理员 / 对该会员具有部门负责人只读视角。
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName user__tasks
|
||||
*
|
||||
* @apiParam {Number} userid 目标会员ID
|
||||
* @apiParam {Number} [owner] 任务身份筛选:1=负责的,0=协作的,不传=全部
|
||||
* @apiParam {Number} [project_id] 仅查询指定项目
|
||||
* @apiParam {Object} [keys] 搜索条件(keys.name 任务名称,keys.status completed/uncompleted)
|
||||
* @apiParam {Number} [page] 当前页,默认1
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function user__tasks()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$targetId = intval(Request::input('userid'));
|
||||
$context = UserDepartment::userWorksContext($viewer, $targetId);
|
||||
if (!$context['allowed']) {
|
||||
return Base::retError('没有查看权限');
|
||||
}
|
||||
$readonly = !$context['is_self'] && !$context['is_admin'];
|
||||
//
|
||||
$owner = Request::input('owner');
|
||||
$owner = is_numeric($owner) ? intval($owner) : null;
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
$keys = Request::input('keys');
|
||||
$keys = is_array($keys) ? $keys : [];
|
||||
//
|
||||
$builder = ProjectTask::with(['taskUser', 'taskTag', 'project:id,name'])
|
||||
->select(['project_tasks.*', 'project_task_users.owner'])
|
||||
->join('project_task_users', function ($join) use ($targetId) {
|
||||
$join->on('project_tasks.id', '=', 'project_task_users.task_id')
|
||||
->where('project_task_users.userid', '=', $targetId);
|
||||
});
|
||||
if ($owner !== null) {
|
||||
$builder->where('project_task_users.owner', $owner);
|
||||
}
|
||||
// 部门负责人视角:限定可见项目集合,且仅"全员可见"(visibility=1)的任务(与 findForDepartmentView 一致,避免列出打不开的任务)
|
||||
if ($readonly) {
|
||||
$builder->whereIn('project_tasks.project_id', $context['project_ids'] ?: [0]);
|
||||
$builder->where('project_tasks.visibility', 1);
|
||||
}
|
||||
if ($project_id > 0) {
|
||||
$builder->where('project_tasks.project_id', $project_id);
|
||||
}
|
||||
if (!empty($keys['name'])) {
|
||||
$builder->where(function ($query) use ($keys) {
|
||||
$query->where('project_tasks.name', 'like', "%{$keys['name']}%")
|
||||
->orWhere('project_tasks.desc', 'like', "%{$keys['name']}%");
|
||||
});
|
||||
}
|
||||
if (!empty($keys['status'])) {
|
||||
if ($keys['status'] == 'completed') {
|
||||
$builder->whereNotNull('project_tasks.complete_at');
|
||||
} elseif ($keys['status'] == 'uncompleted') {
|
||||
$builder->whereNull('project_tasks.complete_at');
|
||||
}
|
||||
}
|
||||
$builder->whereNull('project_tasks.archived_at');
|
||||
//
|
||||
$list = $builder->orderByDesc('project_tasks.id')->paginate(Base::getPaginate(100, 50));
|
||||
$list->transform(function (ProjectTask $task) use ($readonly) {
|
||||
$task->setAppends(['today', 'overdue']);
|
||||
$array = $task->toArray();
|
||||
$array['project_name'] = $array['project']['name'] ?? '';
|
||||
$array['department_readonly'] = $readonly;
|
||||
unset($array['project']);
|
||||
return $array;
|
||||
});
|
||||
//
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/user/counts 会员参与的项目/任务数量
|
||||
*
|
||||
* @apiDescription 需要token身份。用于会员卡片「项目与任务」弹窗的 Tab 角标,仅返回数量(轻量)。
|
||||
* 权限:本人 / 系统管理员 / 对该会员具有部门负责人只读视角。
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName user__counts
|
||||
*
|
||||
* @apiParam {Number} userid 目标会员ID
|
||||
* @apiParam {Number} [owner] 任务身份筛选:1=负责的,0=协作的,不传=全部(仅影响任务数量)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data {project, todo, done}
|
||||
*/
|
||||
public function user__counts()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$targetId = intval(Request::input('userid'));
|
||||
$context = UserDepartment::userWorksContext($viewer, $targetId);
|
||||
if (!$context['allowed']) {
|
||||
return Base::retError('没有查看权限');
|
||||
}
|
||||
$readonly = !$context['is_self'] && !$context['is_admin'];
|
||||
$owner = Request::input('owner');
|
||||
$owner = is_numeric($owner) ? intval($owner) : null;
|
||||
//
|
||||
$projectBuilder = Project::join('project_users', function ($join) use ($targetId) {
|
||||
$join->on('projects.id', '=', 'project_users.project_id')
|
||||
->where('project_users.userid', '=', $targetId);
|
||||
})
|
||||
->whereNull('projects.archived_at');
|
||||
if ($readonly) {
|
||||
$projectBuilder->whereIn('projects.id', $context['project_ids'] ?: [0]);
|
||||
}
|
||||
$projectCount = $projectBuilder->distinct()->count('projects.id');
|
||||
//
|
||||
$taskBuilder = function () use ($targetId, $owner, $readonly, $context) {
|
||||
$builder = ProjectTask::join('project_task_users', function ($join) use ($targetId) {
|
||||
$join->on('project_tasks.id', '=', 'project_task_users.task_id')
|
||||
->where('project_task_users.userid', '=', $targetId);
|
||||
})
|
||||
->whereNull('project_tasks.archived_at');
|
||||
if ($owner !== null) {
|
||||
$builder->where('project_task_users.owner', $owner);
|
||||
}
|
||||
if ($readonly) {
|
||||
$builder->whereIn('project_tasks.project_id', $context['project_ids'] ?: [0]);
|
||||
$builder->where('project_tasks.visibility', 1);
|
||||
}
|
||||
return $builder;
|
||||
};
|
||||
$todoCount = $taskBuilder()->whereNull('project_tasks.complete_at')->count();
|
||||
$doneCount = $taskBuilder()->whereNotNull('project_tasks.complete_at')->count();
|
||||
//
|
||||
return Base::retSuccess('success', [
|
||||
'project' => $projectCount,
|
||||
'todo' => $todoCount,
|
||||
'done' => $doneCount,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/easylists 任务列表-简单的
|
||||
*
|
||||
|
||||
@ -69,8 +69,6 @@ class SystemController extends AbstractController
|
||||
'login_code',
|
||||
'password_policy',
|
||||
'project_invite',
|
||||
'project_add_permission',
|
||||
'project_add_userids',
|
||||
'chat_information',
|
||||
'anon_message',
|
||||
'convert_video',
|
||||
@ -156,10 +154,6 @@ class SystemController extends AbstractController
|
||||
$setting['department_owner_project_view'] = $setting['department_owner_project_view'] ?: 'close';
|
||||
$setting['server_timezone'] = config('app.timezone');
|
||||
$setting['server_version'] = Base::getVersion();
|
||||
// 指定人员名单仅管理员可见
|
||||
if ($type != 'all' && $type != 'save') {
|
||||
unset($setting['project_add_userids']);
|
||||
}
|
||||
//
|
||||
return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
@ -884,8 +884,7 @@ class UsersController extends AbstractController
|
||||
*/
|
||||
public function extra()
|
||||
{
|
||||
$viewer = User::auth();
|
||||
$user = $viewer;
|
||||
$user = User::auth();
|
||||
//
|
||||
$userid = intval(Request::input('userid'));
|
||||
if ($userid <= 0) {
|
||||
@ -920,8 +919,6 @@ class UsersController extends AbstractController
|
||||
|
||||
$tagMeta = UserTag::listWithMeta($userid, $user);
|
||||
|
||||
$worksContext = UserDepartment::userWorksContext($viewer, $userid);
|
||||
|
||||
$data = [
|
||||
'userid' => $userid,
|
||||
'birthday' => $birthday,
|
||||
@ -929,7 +926,6 @@ class UsersController extends AbstractController
|
||||
'introduction' => $introduction,
|
||||
'personal_tags' => $tagMeta['top'],
|
||||
'personal_tags_total' => $tagMeta['total'],
|
||||
'works_visible' => $worksContext['allowed'],
|
||||
];
|
||||
|
||||
return Base::retSuccess('success', $data);
|
||||
@ -1098,8 +1094,6 @@ class UsersController extends AbstractController
|
||||
* - clearadmin 取消管理员
|
||||
* - settemp 设为临时帐号
|
||||
* - cleartemp 取消临时身份(取消临时帐号)
|
||||
* - setverity 标记邮箱为已认证
|
||||
* - clearverity 标记邮箱为未认证
|
||||
* - checkin_macs 修改自动签到mac地址(需要参数 checkin_macs)
|
||||
* - checkin_face 修改签到人脸图片(需要参数 checkin_face)
|
||||
* - department 修改部门(需要参数 department)
|
||||
@ -1163,16 +1157,6 @@ class UsersController extends AbstractController
|
||||
$upArray['identity'] = array_diff($userInfo->identity, ['temp']);
|
||||
break;
|
||||
|
||||
case 'setverity':
|
||||
$msg = '设置成功';
|
||||
$upArray['email_verity'] = 1;
|
||||
break;
|
||||
|
||||
case 'clearverity':
|
||||
$msg = '取消成功';
|
||||
$upArray['email_verity'] = 0;
|
||||
break;
|
||||
|
||||
case 'checkin_macs':
|
||||
$list = is_array($data['checkin_macs']) ? $data['checkin_macs'] : [];
|
||||
$array = [];
|
||||
@ -1370,7 +1354,6 @@ class UsersController extends AbstractController
|
||||
* @apiParam {String} email 邮箱
|
||||
* @apiParam {String} password 初始密码
|
||||
* @apiParam {String} nickname 昵称
|
||||
* @apiParam {Number} [email_verity] 是否标记邮箱为已认证(1是、0否,默认1)
|
||||
* @apiParam {String} [profession] 职位/职称(可选,2-20字)
|
||||
* @apiParam {Array} [department] 部门ID列表(可选,最多10个)
|
||||
*/
|
||||
@ -1381,12 +1364,10 @@ class UsersController extends AbstractController
|
||||
$password = trim(Request::input('password'));
|
||||
$nickname = trim(Request::input('nickname'));
|
||||
$changePass = intval(Request::input('changepass', 1)) === 1;
|
||||
$emailVerity = intval(Request::input('email_verity', 1)) === 1;
|
||||
$profession = trim((string)Request::input('profession', ''));
|
||||
$department = Request::input('department', []);
|
||||
$user = User::createByAdmin($email, $password, $nickname, [
|
||||
'changePass' => $changePass,
|
||||
'emailVerity' => $emailVerity,
|
||||
'profession' => $profession,
|
||||
'department' => is_array($department) ? $department : [],
|
||||
]);
|
||||
@ -1424,7 +1405,7 @@ class UsersController extends AbstractController
|
||||
/**
|
||||
* @api {post} api/users/import 批量导入用户(管理员)
|
||||
*
|
||||
* @apiDescription 需要token身份(管理员)。提交预览确认后的行数据 rows(每行 {email,nickname,password,profession},可选 department[]、email_verity(1已认证/0未认证,默认0))进行创建
|
||||
* @apiDescription 需要token身份(管理员)。提交预览确认后的行数据 rows(每行 {email,nickname,password,profession},可选 department[])进行创建
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName import
|
||||
|
||||
@ -10,19 +10,14 @@ class TrustProxies extends Middleware
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* PHP(Swoole)只在内网被 nginx 访问,外部无法直连,故信任内网代理。
|
||||
*
|
||||
* @var array|string|null
|
||||
*/
|
||||
protected $proxies = '*';
|
||||
protected $proxies;
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* 只采信 X-Forwarded-Proto:nginx 已用 $the_scheme 覆盖该头(值由 nginx 控制),
|
||||
* 据此让 url() 实时跟随 https;host/for 一律不信,避免 Host 注入与 IP 伪造。
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers = Request::HEADER_X_FORWARDED_PROTO;
|
||||
protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
||||
|
||||
@ -25,6 +25,14 @@ class WebApi
|
||||
RequestContext::set('start_time', microtime(true));
|
||||
RequestContext::set('header_language', $request->header('language'));
|
||||
|
||||
// 强制 https
|
||||
$APP_SCHEME = env('APP_SCHEME', 'auto');
|
||||
if (in_array(strtolower($APP_SCHEME), ['https', 'on', 'ssl', '1', 'true', 'yes'], true)) {
|
||||
$request->server->set('HTTPS', 'on');
|
||||
$request->headers->set('X-Forwarded-Proto', 'https');
|
||||
$request->setTrustedProxies([$request->getClientIp()], $request::HEADER_X_FORWARDED_PROTO);
|
||||
}
|
||||
|
||||
// 更新请求的基本URL
|
||||
RequestContext::updateBaseUrl($request);
|
||||
|
||||
|
||||
@ -605,38 +605,6 @@ class Project extends AbstractModel
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否有权限创建项目(依据系统设置「项目创建权限」)
|
||||
* @param int $userid
|
||||
* @return bool
|
||||
*/
|
||||
public static function userCanCreate($userid)
|
||||
{
|
||||
// 范围已在 Setting::getSettingAttribute() 归一化(默认 ['all'])
|
||||
$modes = Base::settingFind('system', 'project_add_permission', ['all']);
|
||||
// 「所有人」:放行(与具体用户无关,避免未携带身份时被误判为无权)
|
||||
if (in_array('all', $modes)) {
|
||||
return true;
|
||||
}
|
||||
$user = User::find(intval($userid));
|
||||
if (empty($user)) {
|
||||
return false;
|
||||
}
|
||||
// 系统管理员始终可创建项目(不受开关限制)
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
// 部门负责人/部门管理员
|
||||
if (in_array('departmentOwner', $modes) && UserDepartment::getManagedDepartments($user->userid)->isNotEmpty()) {
|
||||
return true;
|
||||
}
|
||||
// 指定人员
|
||||
if (in_array('appoint', $modes)) {
|
||||
return in_array($user->userid, Base::settingFind('system', 'project_add_userids', []));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
* @param $params
|
||||
@ -653,10 +621,6 @@ class Project extends AbstractModel
|
||||
$desc = trim(Arr::get($params, 'desc', ''));
|
||||
$flow = trim(Arr::get($params, 'flow', 'close'));
|
||||
$isPersonal = intval(Arr::get($params, 'personal'));
|
||||
// 个人项目为系统自动创建,不受创建权限限制
|
||||
if (!$isPersonal && !self::userCanCreate($userid)) {
|
||||
return Base::retError('当前仅指定人员可以创建项目');
|
||||
}
|
||||
if (mb_strlen($name) < 2) {
|
||||
return Base::retError('项目名称不可以少于2个字');
|
||||
} elseif (mb_strlen($name) > 32) {
|
||||
|
||||
@ -59,14 +59,6 @@ class Setting extends AbstractModel
|
||||
if (!is_array($value['task_default_time']) || count($value['task_default_time']) != 2 || !Timer::isTime($value['task_default_time'][0]) || !Timer::isTime($value['task_default_time'][1])) {
|
||||
$value['task_default_time'] = ['09:00', '18:00'];
|
||||
}
|
||||
// 项目创建权限:范围(all/departmentOwner/appoint,默认 all)+ 指定人员
|
||||
$value['project_add_permission'] = array_values(array_intersect(
|
||||
is_array($value['project_add_permission'] ?? null) ? $value['project_add_permission'] : [],
|
||||
['all', 'departmentOwner', 'appoint']
|
||||
)) ?: ['all'];
|
||||
$value['project_add_userids'] = is_array($value['project_add_userids'] ?? null)
|
||||
? array_values(array_unique(array_filter(array_map('intval', $value['project_add_userids']))))
|
||||
: [];
|
||||
break;
|
||||
|
||||
// 文件设置
|
||||
|
||||
@ -432,7 +432,7 @@ class User extends AbstractModel
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @param string $nickname
|
||||
* @param array $options changePass(bool,默认true) / emailVerity(bool,默认false,标记邮箱已认证) / department(int[]) / profession(string)
|
||||
* @param array $options changePass(bool,默认true) / department(int[]) / profession(string)
|
||||
* @return self
|
||||
* @throws ApiException
|
||||
*/
|
||||
@ -443,7 +443,6 @@ class User extends AbstractModel
|
||||
throw new ApiException('昵称需为2-20个字');
|
||||
}
|
||||
$changePass = ($options['changePass'] ?? true) ? 1 : 0;
|
||||
$emailVerity = ($options['emailVerity'] ?? false) ? 1 : 0;
|
||||
$profession = trim((string)($options['profession'] ?? ''));
|
||||
// 校验前置(reg 之前快速失败,且可在无 Swoole 环境单测)
|
||||
self::assertValidProfession($profession);
|
||||
@ -455,7 +454,6 @@ class User extends AbstractModel
|
||||
$user->identity = Base::arrayImplode(array_diff($user->identity, ['temp']));
|
||||
}
|
||||
$user->changepass = $changePass; // 复用现有首登强制改密机制
|
||||
$user->email_verity = $emailVerity; // 管理员可在创建时直接标记邮箱认证状态
|
||||
if ($profession !== '') {
|
||||
$user->profession = $profession;
|
||||
}
|
||||
@ -621,7 +619,6 @@ class User extends AbstractModel
|
||||
try {
|
||||
self::createByAdmin($row['email'], $row['password'], $row['nickname'], [
|
||||
'changePass' => $changePass,
|
||||
'emailVerity' => !empty($row['email_verity']),
|
||||
'department' => $row['department'] ?? [],
|
||||
'profession' => $row['profession'] ?? '',
|
||||
]);
|
||||
@ -695,7 +692,6 @@ class User extends AbstractModel
|
||||
'nickname' => $row['nickname'] ?? '',
|
||||
'password' => $row['password'] ?? '',
|
||||
'profession' => $row['profession'] ?? '',
|
||||
'email_verity' => 1, // 默认标记为已认证,前端可在预览中按行调整
|
||||
'status' => $ok ? 'ok' : 'error',
|
||||
'reason' => $reason ?? '',
|
||||
];
|
||||
|
||||
@ -615,69 +615,4 @@ class UserDepartment extends AbstractModel
|
||||
return $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员卡片「查看该会员项目/任务」的权限上下文。
|
||||
* 允许条件:本人 / 系统管理员 / 对该会员具有部门负责人只读视角。
|
||||
* @param User $viewer 当前登录用户
|
||||
* @param int $targetUserid 目标会员
|
||||
* @return array ['allowed'=>bool, 'is_self'=>bool, 'is_admin'=>bool, 'project_ids'=>int[]]
|
||||
* project_ids 仅在部门负责人视角下有意义(限定可见项目集合);本人/管理员为空数组表示不限制
|
||||
*/
|
||||
public static function userWorksContext(User $viewer, int $targetUserid): array
|
||||
{
|
||||
$result = [
|
||||
'allowed' => false,
|
||||
'is_self' => false,
|
||||
'is_admin' => false,
|
||||
'project_ids' => [],
|
||||
];
|
||||
if ($targetUserid <= 0) {
|
||||
return $result;
|
||||
}
|
||||
// 机器人/系统账号(或不存在)不展示项目与任务
|
||||
$target = User::select(['userid', 'bot'])->whereUserid($targetUserid)->first();
|
||||
if (empty($target) || $target->bot) {
|
||||
return $result;
|
||||
}
|
||||
// 本人
|
||||
if ($viewer->userid === $targetUserid) {
|
||||
$result['allowed'] = true;
|
||||
$result['is_self'] = true;
|
||||
return $result;
|
||||
}
|
||||
// 系统管理员
|
||||
if ($viewer->isAdmin()) {
|
||||
$result['allowed'] = true;
|
||||
$result['is_admin'] = true;
|
||||
return $result;
|
||||
}
|
||||
// 部门负责人只读视角
|
||||
if (Base::settingFind('system', 'department_owner_project_view', 'close') !== 'open') {
|
||||
return $result;
|
||||
}
|
||||
$memberUserids = self::getManagedMemberUserids($viewer->userid, 'all');
|
||||
if (!in_array($targetUserid, $memberUserids, true)) {
|
||||
return $result;
|
||||
}
|
||||
// 目标会员参与、且未关闭「部门负责人视角可见」的项目
|
||||
$projectIds = ProjectUser::where('project_users.userid', $targetUserid)
|
||||
->join('projects', 'projects.id', '=', 'project_users.project_id')
|
||||
->whereNull('projects.deleted_at')
|
||||
->where(function ($query) {
|
||||
$query->where('projects.department_owner_view', '<>', 'close')
|
||||
->orWhereNull('projects.department_owner_view');
|
||||
})
|
||||
->distinct()
|
||||
->pluck('projects.id')
|
||||
->map(fn($v) => intval($v))
|
||||
->values()
|
||||
->toArray();
|
||||
if (empty($projectIds)) {
|
||||
return $result;
|
||||
}
|
||||
$result['allowed'] = true;
|
||||
$result['project_ids'] = $projectIds;
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -62,9 +62,6 @@ class WebSocketDialog extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
// 全员群初始化默认名称(双语字面量),用于识别"管理员尚未自定义"的状态
|
||||
const ALL_GROUP_DEFAULT_NAME = '全体成员 All members';
|
||||
|
||||
protected $appends = ['deputy_ids'];
|
||||
|
||||
/**
|
||||
@ -369,9 +366,7 @@ class WebSocketDialog extends AbstractModel
|
||||
}
|
||||
break;
|
||||
case 'all':
|
||||
$data['name'] = ($data['name'] && $data['name'] !== self::ALL_GROUP_DEFAULT_NAME)
|
||||
? $data['name']
|
||||
: Doo::translate('全体成员');
|
||||
$data['name'] = Doo::translate('全体成员');
|
||||
$data['dialog_mute'] = Base::settingFind('system', 'all_group_mute');
|
||||
break;
|
||||
}
|
||||
@ -728,10 +723,6 @@ class WebSocketDialog extends AbstractModel
|
||||
if ($userid <= 0) {
|
||||
return false;
|
||||
}
|
||||
// 系统管理员:可管理任意会话的他人待办(与管理员全局管理能力一致,覆盖无群主的全员群等)
|
||||
if (User::find($userid)?->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
// 群主 / 群管理员
|
||||
if ($this->isOwner($userid)) {
|
||||
return true;
|
||||
@ -829,9 +820,7 @@ class WebSocketDialog extends AbstractModel
|
||||
$name = \DB::table('project_tasks')->where('dialog_id', $this->id)->value('name');
|
||||
break;
|
||||
case 'all':
|
||||
$name = ($name && $name !== self::ALL_GROUP_DEFAULT_NAME)
|
||||
? $name
|
||||
: Doo::translate('全体成员');
|
||||
$name = Doo::translate('全体成员');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -428,7 +428,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$affected = array_unique(array_merge($cancel, $setup)); // 本次真正影响到的用户
|
||||
$others = array_diff($affected, [$sender]); // 排除"自己"
|
||||
if ($others && !$dialog->checkTodoOwnerPermission($sender)) {
|
||||
return Base::retError('仅群主、项目/任务负责人或系统管理员可设置或取消他人待办');
|
||||
return Base::retError('仅群主、项目/任务负责人可设置或取消他人待办');
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
@ -14,7 +14,7 @@ use Overtrue\Pinyin\Pinyin;
|
||||
use Redirect;
|
||||
use Request;
|
||||
use Storage;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Validator;
|
||||
@ -848,13 +848,6 @@ class Base
|
||||
*/
|
||||
public static function getSchemeAndHost()
|
||||
{
|
||||
// 优先用当前请求的协议+主机:getScheme() 会经 TrustProxies 采信 X-Forwarded-Proto,
|
||||
// 从而正确识别 https;host 取自 Host 头(不信 X-Forwarded-Host,避免 Host 注入)
|
||||
$request = request();
|
||||
if ($request && $request->getHttpHost()) {
|
||||
return $request->getSchemeAndHttpHost();
|
||||
}
|
||||
// 非请求上下文(Task/命令行等)的兜底
|
||||
$scheme = isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443' ? 'https://' : 'http://';
|
||||
return $scheme.($_SERVER['HTTP_HOST'] ?? '');
|
||||
}
|
||||
@ -2825,17 +2818,14 @@ class Base
|
||||
|
||||
/**
|
||||
* 字节转格式
|
||||
* @param int|float $bytes
|
||||
* @param $bytes
|
||||
* @return string
|
||||
*/
|
||||
public static function readableBytes(int|float $bytes): string
|
||||
public static function readableBytes($bytes)
|
||||
{
|
||||
if ($bytes <= 0) {
|
||||
return '0 B';
|
||||
}
|
||||
$i = (int) floor(log($bytes) / log(1024));
|
||||
$i = floor(log($bytes) / log(1024));
|
||||
$sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
return (string) ((float) sprintf('%.02F', $bytes / pow(1024, $i))) . ' ' . $sizes[$i];
|
||||
return sprintf('%.02F', $bytes / pow(1024, $i)) * 1 . ' ' . $sizes[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2878,15 +2868,9 @@ class Base
|
||||
|
||||
/**
|
||||
* DownloadFileResponse 下载文件
|
||||
*
|
||||
* 返回 Symfony BinaryFileResponse,在 LaravelS/Swoole 环境下由 StaticResponse 走原生
|
||||
* sendfile() 发送——OS 级零拷贝、不占用 PHP 内存,可支持任意大小文件(如几百 MB 的大文件)。
|
||||
* 切勿改回 StreamedResponse:它会被 LaravelS 用 ob_start()/ob_get_clean() 把整个响应体
|
||||
* 缓冲进 PHP 内存,大文件会撞 memory_limit 导致下载失败。
|
||||
*
|
||||
* @param File|\SplFileInfo|string $file 文件对象或路径
|
||||
* @param string|null $name 下载文件名
|
||||
* @return BinaryFileResponse
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
public static function DownloadFileResponse($file, $name = null)
|
||||
{
|
||||
@ -2905,6 +2889,12 @@ class Base
|
||||
throw new FileException('File must be readable and exist.');
|
||||
}
|
||||
|
||||
// 获取文件信息
|
||||
$size = $file->getSize();
|
||||
if ($size === false || $size < 0) {
|
||||
throw new FileException('Unable to determine file size.');
|
||||
}
|
||||
|
||||
// 处理文件名
|
||||
if (empty($name)) {
|
||||
$name = basename($file->getPathname());
|
||||
@ -2922,27 +2912,83 @@ class Base
|
||||
$mimeType = 'application/octet-stream';
|
||||
}
|
||||
|
||||
// BinaryFileResponse:autoEtag=false 避免对大文件做 md5/sha1 全文件哈希,autoLastModified=true 取 mtime(开销极小)
|
||||
$response = new BinaryFileResponse($file, 200, [], true, null, false, true);
|
||||
$response->headers->set('Content-Type', $mimeType);
|
||||
$response->headers->set('Cache-Control', 'private, no-transform, no-store, must-revalidate, max-age=0');
|
||||
// filename 兜底为纯 ASCII,filename* 用 UTF-8 编码,兼容含中文/特殊字符的文件名
|
||||
$asciiName = preg_replace('/[^\x20-\x7e]/', '_', $name);
|
||||
$response->headers->set('Content-Disposition', sprintf(
|
||||
'attachment; filename="%s"; filename*=UTF-8\'\'%s',
|
||||
$asciiName,
|
||||
rawurlencode($name)
|
||||
));
|
||||
// 处理 Range 请求
|
||||
$start = 0;
|
||||
$end = $size - 1;
|
||||
$length = $size;
|
||||
$isRangeRequest = false;
|
||||
|
||||
// LaravelS/Swoole 下 StaticResponse 用 sendfile() 整文件发送,不支持分段;
|
||||
// 若放任 Symfony 处理 Range 会返回 206 头却仍发送完整文件,导致内容错位/损坏。
|
||||
// 故在 Swoole 环境下移除 Range 请求头,始终以 200 返回完整文件。
|
||||
if (app()->bound('swoole')) {
|
||||
Request::instance()->headers->remove('Range');
|
||||
$response->headers->set('Accept-Ranges', 'none');
|
||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||
$range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']);
|
||||
if (preg_match('/^(\d+)-(\d*)$/', $range, $matches)) {
|
||||
$start = intval($matches[1]);
|
||||
$end = !empty($matches[2]) ? intval($matches[2]) : $size - 1;
|
||||
|
||||
// 验证范围的有效性
|
||||
if ($start >= 0 && $end < $size && $start <= $end) {
|
||||
$length = $end - $start + 1;
|
||||
$isRangeRequest = true;
|
||||
} else {
|
||||
$start = 0;
|
||||
$end = $size - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
// 设置基本响应头
|
||||
$headers = [
|
||||
'Content-Type' => $mimeType,
|
||||
'Content-Disposition' => sprintf(
|
||||
'attachment; filename="%s"; filename*=UTF-8\'\'%s',
|
||||
$name,
|
||||
rawurlencode($name)
|
||||
),
|
||||
'Accept-Ranges' => 'bytes',
|
||||
'Cache-Control' => 'private, no-transform, no-store, must-revalidate, max-age=0',
|
||||
'Content-Length' => $length,
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s', $file->getMTime()) . ' GMT',
|
||||
'ETag' => sprintf('"%s"', md5_file($file->getPathname()))
|
||||
];
|
||||
|
||||
if ($isRangeRequest) {
|
||||
$headers['Content-Range'] = "bytes {$start}-{$end}/{$size}";
|
||||
$statusCode = 206;
|
||||
} else {
|
||||
$statusCode = 200;
|
||||
}
|
||||
|
||||
// 创建流式响应
|
||||
return new StreamedResponse(
|
||||
function () use ($file, $start, $length) {
|
||||
$handle = fopen($file->getPathname(), 'rb');
|
||||
if ($handle === false) {
|
||||
throw new FileException('Cannot open file for reading');
|
||||
}
|
||||
|
||||
if (fseek($handle, $start) === -1) {
|
||||
fclose($handle);
|
||||
throw new FileException('Cannot seek to position ' . $start);
|
||||
}
|
||||
|
||||
$remaining = $length;
|
||||
$bufferSize = 8192; // 8KB chunks
|
||||
|
||||
while ($remaining > 0 && !feof($handle)) {
|
||||
$readSize = min($bufferSize, $remaining);
|
||||
$buffer = fread($handle, $readSize);
|
||||
if ($buffer === false) {
|
||||
break;
|
||||
}
|
||||
echo $buffer;
|
||||
flush();
|
||||
$remaining -= strlen($buffer);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
},
|
||||
$statusCode,
|
||||
$headers
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('File download failed', [
|
||||
'error' => $e->getMessage(),
|
||||
|
||||
@ -189,12 +189,7 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
if ($umengUserid) {
|
||||
$setting = Base::setting('appPushSetting');
|
||||
if ($setting['push'] === 'open') {
|
||||
if ($msg->userid == -1) {
|
||||
// AI 助手虚拟用户没有会员记录,取自定义昵称或默认名称
|
||||
$umengTitle = ($msg->msg['nickname'] ?? '') ?: Doo::translate('AI 助手');
|
||||
} else {
|
||||
$umengTitle = User::userid2nickname($msg->userid);
|
||||
}
|
||||
$umengTitle = User::userid2nickname($msg->userid);
|
||||
$umengBody = WebSocketDialogMsg::previewMsg($msg);
|
||||
if ($dialog->type == 'group') {
|
||||
$umengBody = $umengTitle . ': ' . $umengBody;
|
||||
|
||||
347
bin/version.js
vendored
Normal file
347
bin/version.js
vendored
Normal file
@ -0,0 +1,347 @@
|
||||
const fs = require('fs');
|
||||
const path = require("path");
|
||||
const exec = require('child_process').exec;
|
||||
let ProxyAgent = null;
|
||||
try {
|
||||
ProxyAgent = require("undici").ProxyAgent;
|
||||
} catch (error) {
|
||||
ProxyAgent = null;
|
||||
}
|
||||
const packageFile = path.resolve(process.cwd(), "package.json");
|
||||
const changeFile = path.resolve(process.cwd(), "CHANGELOG.md");
|
||||
|
||||
const verOffset = 6394; // 版本号偏移量
|
||||
const codeOffset = 35; // 代码版本号偏移量
|
||||
|
||||
const envFilePath = path.resolve(process.cwd(), ".env");
|
||||
const defaultAiSystemPrompt = "你是一位软件发布日志编辑专家。请产出 Markdown 更新日志,面向普通用户,以通俗友好的简体中文描述更新带来的直接好处,避免技术术语。所有章节标题必须以 `### ` 开头并保持英文 Title Case(例如 `### Features`、`### Bug Fixes`、`### Performance`、`### Documentation` 等)。每个章节内的条目按用户价值和影响范围排序,将更重要、影响更广的更新放在前面。";
|
||||
const defaultOpenAiEndpoint = "https://api.openai.com/v1/chat/completions";
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return;
|
||||
}
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
content.split(/\r?\n/).forEach(rawLine => {
|
||||
const line = rawLine.trim();
|
||||
if (!line || line.startsWith("#")) {
|
||||
return;
|
||||
}
|
||||
const equalsIndex = line.indexOf("=");
|
||||
if (equalsIndex === -1) {
|
||||
return;
|
||||
}
|
||||
let key = line.slice(0, equalsIndex).trim();
|
||||
if (key.startsWith("export ")) {
|
||||
key = key.slice(7).trim();
|
||||
}
|
||||
let value = line.slice(equalsIndex + 1).trim();
|
||||
if (!value) {
|
||||
value = "";
|
||||
}
|
||||
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
} else {
|
||||
const commentIndex = value.indexOf(" #");
|
||||
if (commentIndex !== -1) {
|
||||
value = value.slice(0, commentIndex).trim();
|
||||
}
|
||||
}
|
||||
if (process.env[key] === undefined) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadEnvFile(envFilePath);
|
||||
|
||||
function resolveApiEndpoint(candidate) {
|
||||
const source = (candidate || "").trim();
|
||||
if (!source) {
|
||||
return defaultOpenAiEndpoint;
|
||||
}
|
||||
if (/\/chat\/completions(\?|$)/.test(source)) {
|
||||
return source;
|
||||
}
|
||||
const normalized = source.replace(/\/+$/, "");
|
||||
if (/\/v\d+$/i.test(normalized)) {
|
||||
return `${normalized}/chat/completions`;
|
||||
}
|
||||
return `${normalized}/v1/chat/completions`;
|
||||
}
|
||||
|
||||
function loadSocksProxyAgent(proxyUrl) {
|
||||
try {
|
||||
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||
return new SocksProxyAgent(proxyUrl);
|
||||
} catch (error) {
|
||||
if (error && error.code === 'MODULE_NOT_FOUND') {
|
||||
console.warn("检测到 SOCKS 代理,但未安装 socks-proxy-agent,请运行 `npm install --save-dev socks-proxy-agent` 后重试。");
|
||||
} else {
|
||||
console.warn(`无法初始化 SOCKS 代理: ${error?.message || error}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function createProxyDispatcher(proxyUrl) {
|
||||
if (!proxyUrl) {
|
||||
return null;
|
||||
}
|
||||
let parsedProtocol = '';
|
||||
try {
|
||||
parsedProtocol = new URL(proxyUrl).protocol.replace(':', '').toLowerCase();
|
||||
} catch (error) {
|
||||
console.warn(`代理地址无效 (${proxyUrl}): ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
if (parsedProtocol.startsWith('socks')) {
|
||||
return loadSocksProxyAgent(proxyUrl);
|
||||
}
|
||||
if (!ProxyAgent) {
|
||||
console.warn('未找到 undici.ProxyAgent,无法启用 HTTP 代理。');
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new ProxyAgent(proxyUrl);
|
||||
} catch (error) {
|
||||
console.warn(`无法初始化代理 (${proxyUrl}): ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function buildDefaultUserPrompt(version, changelogSection) {
|
||||
return [
|
||||
"你是一位软件发布日志编辑专家。",
|
||||
"下面是一段通过 git 提交记录自动生成的更新日志文本。",
|
||||
"",
|
||||
"请将其整理为一份「面向普通用户、简洁概览风格」的 changelog,保持 Markdown 格式,包含以下结构:",
|
||||
"",
|
||||
`## [${version}]`,
|
||||
"",
|
||||
"### Features",
|
||||
"",
|
||||
"- ...",
|
||||
"",
|
||||
"### Bug Fixes",
|
||||
"",
|
||||
"- ...",
|
||||
"",
|
||||
"### Performance",
|
||||
"",
|
||||
"- ...",
|
||||
"",
|
||||
"**要求:**",
|
||||
"1. 删除技术性或重复的细节,合并相似项。",
|
||||
"2. 语句自然简洁,用简体中文描述。",
|
||||
"3. 使用贴近日常的词汇,突出更新对普通用户的直接价值,避免开发或管理术语(如\"refactor\"、\"merge branch\"、\"commit lint\")。",
|
||||
"4. 小节标题必须以 `### ` 开头并保持英文 Title Case(例如 `### Features`、`### Bug Fixes`、`### Performance`、`### Documentation`、`### Security`、`### Miscellaneous` 等),不得翻译成中文。",
|
||||
"5. 每个小节内的条目按用户价值和影响范围排序,将更重要、影响更广的更新放在前面。",
|
||||
"6. 若某个小节没有内容,请省略整段小节(包括标题)。",
|
||||
"7. 输出仅为 Markdown changelog 内容,不加其他解释。",
|
||||
"",
|
||||
"以下是原始日志:",
|
||||
"```markdown",
|
||||
changelogSection,
|
||||
"```"
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function runExec(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(command, { maxBuffer: 1024 * 1024 * 10 }, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(stdout.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeDuplicateLines(log) {
|
||||
const logs = log.split(/(\n## \[.*?\])/);
|
||||
return logs.map(str => {
|
||||
const array = [];
|
||||
const items = str.split("\n");
|
||||
items.forEach(item => {
|
||||
if (/^-/.test(item)) {
|
||||
if (array.indexOf(item) === -1) {
|
||||
array.push(item);
|
||||
}
|
||||
} else {
|
||||
array.push(item);
|
||||
}
|
||||
});
|
||||
return array.join("\n");
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function findSectionBounds(content, version) {
|
||||
const heading = `## [${version}]`;
|
||||
const start = content.indexOf(heading);
|
||||
if (start === -1) {
|
||||
return null;
|
||||
}
|
||||
const nextHeadingIndex = content.indexOf("\n## [", start + heading.length);
|
||||
const end = nextHeadingIndex === -1 ? content.length : nextHeadingIndex;
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
function trimCliffOutput(rawOutput, version) {
|
||||
const markerIndex = rawOutput.indexOf("## [");
|
||||
if (markerIndex === -1) {
|
||||
return "";
|
||||
}
|
||||
return rawOutput
|
||||
.slice(markerIndex)
|
||||
.replace("## [Unreleased]", `## [${version}]`)
|
||||
.trim();
|
||||
}
|
||||
|
||||
function buildAiHeaders(apiUrl, apiKey) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
const customHeader = process.env.CHANGELOG_AI_AUTH_HEADER;
|
||||
if (customHeader) {
|
||||
const separatorIndex = customHeader.indexOf(":");
|
||||
if (separatorIndex !== -1) {
|
||||
const headerName = customHeader.slice(0, separatorIndex).trim();
|
||||
const headerValue = customHeader.slice(separatorIndex + 1).trim();
|
||||
if (headerName && headerValue) {
|
||||
headers[headerName] = headerValue;
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
if (apiUrl.includes("openai.azure.com")) {
|
||||
headers["api-key"] = apiKey;
|
||||
} else {
|
||||
headers.Authorization = `Bearer ${apiKey}`;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function enhanceWithAI(version, changelogSection) {
|
||||
const apiKey = (process.env.OPENAI_API_KEY || "").trim();
|
||||
if (!apiKey) {
|
||||
console.warn("未设置 OPENAI_API_KEY,跳过 AI 发布日志整理。");
|
||||
return changelogSection;
|
||||
}
|
||||
const proxyUrl = (process.env.OPENAI_PROXY_URL || "").trim();
|
||||
const explicitApiUrl = process.env.CHANGELOG_AI_URL || process.env.OPENAI_API_URL || process.env.OPENAI_BASE_URL;
|
||||
const apiUrl = resolveApiEndpoint(explicitApiUrl);
|
||||
const dispatcher = createProxyDispatcher(proxyUrl);
|
||||
const model = process.env.CHANGELOG_AI_MODEL || process.env.OPENAI_API_MODEL || "gpt-4o-mini";
|
||||
const systemPrompt = process.env.CHANGELOG_AI_SYSTEM_PROMPT || defaultAiSystemPrompt;
|
||||
const userPrompt = process.env.CHANGELOG_AI_PROMPT || buildDefaultUserPrompt(version, changelogSection);
|
||||
|
||||
try {
|
||||
const requestInit = {
|
||||
method: "POST",
|
||||
headers: buildAiHeaders(apiUrl, apiKey),
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: userPrompt }
|
||||
],
|
||||
})
|
||||
};
|
||||
if (dispatcher) {
|
||||
requestInit.dispatcher = dispatcher;
|
||||
}
|
||||
const response = await fetch(apiUrl, requestInit);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`AI request failed: ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const aiText = data?.choices?.[0]?.message?.content?.trim();
|
||||
if (!aiText) {
|
||||
throw new Error("AI response did not contain content.");
|
||||
}
|
||||
return aiText
|
||||
.replace(/^\s*```markdown\s*/i, "")
|
||||
.replace(/\s*```\s*$/i, "")
|
||||
.trim();
|
||||
} catch (error) {
|
||||
console.warn("AI summarization failed, falling back to original section:", error.message);
|
||||
return changelogSection;
|
||||
}
|
||||
}
|
||||
|
||||
async function generateLatestSection(version) {
|
||||
const rawOutput = await runExec('docker run -t --rm -v "$(pwd)":/app/ orhunp/git-cliff:1.3.0 --unreleased');
|
||||
const section = trimCliffOutput(rawOutput, version);
|
||||
if (!section.trim() || section.trim() === `## [${version}]`) {
|
||||
return "";
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
function insertChangelogSection(existing, section, version) {
|
||||
const trimmedSection = section.trim();
|
||||
if (!trimmedSection) {
|
||||
return existing;
|
||||
}
|
||||
const bounds = findSectionBounds(existing, version);
|
||||
if (bounds) {
|
||||
return `${existing.slice(0, bounds.start)}${trimmedSection}\n\n${existing.slice(bounds.end).replace(/^(\n)+/, "")}`;
|
||||
}
|
||||
const insertIndex = existing.indexOf("\n## [");
|
||||
if (insertIndex === -1) {
|
||||
return `${existing.trimEnd()}\n\n${trimmedSection}\n`;
|
||||
}
|
||||
const head = existing.slice(0, insertIndex).trimEnd();
|
||||
const tail = existing.slice(insertIndex).replace(/^(\n)+/, "");
|
||||
return `${head}\n\n${trimmedSection}\n\n${tail}`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const verCountRaw = await runExec("git rev-list --count HEAD");
|
||||
const codeCountRaw = await runExec("git tag --merged pro -l 'v*' | wc -l");
|
||||
const verCount = verCountRaw.trim();
|
||||
const codeCount = codeCountRaw.trim();
|
||||
|
||||
const num = verOffset + parseInt(verCount, 10);
|
||||
if (Number.isNaN(num) || Math.floor(num % 100) < 0) {
|
||||
throw new Error(`get version error ${verCount}`);
|
||||
}
|
||||
const version = `${Math.floor(num / 10000)}.${Math.floor((num % 10000) / 100)}.${Math.floor(num % 100)}`;
|
||||
const codeVersion = codeOffset + parseInt(codeCount, 10);
|
||||
|
||||
let packageContent = fs.readFileSync(packageFile, "utf8");
|
||||
packageContent = packageContent.replace(/"version":\s*"(.*?)"/, `"version": "${version}"`);
|
||||
packageContent = packageContent.replace(/"codeVerson":(.*?)(,|$)/, `"codeVerson": ${codeVersion}$2`);
|
||||
fs.writeFileSync(packageFile, packageContent, "utf8");
|
||||
|
||||
console.log("New version: " + version);
|
||||
console.log("New code verson: " + codeVersion);
|
||||
|
||||
if (!fs.existsSync(changeFile)) {
|
||||
throw new Error("Change file does not exist");
|
||||
}
|
||||
|
||||
const latestSection = await generateLatestSection(version);
|
||||
if (!latestSection) {
|
||||
console.log("No new changelog entries detected.");
|
||||
return;
|
||||
}
|
||||
|
||||
const aiSection = await enhanceWithAI(version, latestSection);
|
||||
|
||||
const changelogContent = fs.readFileSync(changeFile, "utf8");
|
||||
const mergedContent = insertChangelogSection(changelogContent, aiSection, version);
|
||||
const dedupedContent = removeDuplicateLines(mergedContent);
|
||||
|
||||
fs.writeFileSync(changeFile, dedupedContent.trimEnd() + "\n", "utf8");
|
||||
console.log("Log file updated: CHANGELOG.md");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
60
cliff.toml
Normal file
60
cliff.toml
Normal file
@ -0,0 +1,60 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}]
|
||||
{% else %}\
|
||||
## [Unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^pref", group = "Performance"},
|
||||
{ message = "^refactor", group = "Refactor"},
|
||||
{ message = "^style", group = "Styling"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = "v0.1.0-beta.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags chronologically
|
||||
date_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "newest"
|
||||
6
cmd
6
cmd
@ -882,7 +882,7 @@ case "$1" in
|
||||
else
|
||||
https_auto
|
||||
fi
|
||||
$COMPOSE up -d
|
||||
restart_php
|
||||
;;
|
||||
"artisan")
|
||||
shift 1
|
||||
@ -931,6 +931,10 @@ case "$1" in
|
||||
container_exec php "php app/Models/clearHelper.php"
|
||||
container_exec php "php artisan ide-helper:models -W"
|
||||
;;
|
||||
"translate")
|
||||
shift 1
|
||||
container_exec php "cd /var/www/language && php translate.php"
|
||||
;;
|
||||
"restart")
|
||||
shift 1
|
||||
$COMPOSE stop "$@"
|
||||
|
||||
@ -218,6 +218,6 @@ class WebSocketDialogsTableSeeder extends Seeder
|
||||
User::botGetOrCreate('ai-claude');
|
||||
|
||||
$userids = User::whereBot(0)->whereNull('disable_at')->pluck('userid')->toArray();
|
||||
WebSocketDialog::createGroup(WebSocketDialog::ALL_GROUP_DEFAULT_NAME, $userids, 'all');
|
||||
WebSocketDialog::createGroup("全体成员 All members", $userids, 'all');
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,10 +42,8 @@ services:
|
||||
ports:
|
||||
- "${APP_PORT}:80"
|
||||
- "${APP_SSL_PORT:-0}:443"
|
||||
environment:
|
||||
APP_SCHEME: "${APP_SCHEME:-auto}"
|
||||
volumes:
|
||||
- ./docker/nginx/default.conf:/etc/nginx/templates/default.conf.template
|
||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||
- ./:/var/www
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost/health"]
|
||||
@ -98,7 +96,7 @@ services:
|
||||
appstore:
|
||||
container_name: "dootask-appstore-${APP_ID}"
|
||||
privileged: true
|
||||
image: "dootask/appstore:0.4.3"
|
||||
image: "dootask/appstore:0.4.0"
|
||||
volumes:
|
||||
- shared_data:/usr/share/dootask
|
||||
- ${HOST_DOCKER_SOCK:-/var/run/docker.sock}:/var/run/docker.sock
|
||||
|
||||
@ -1,47 +1,23 @@
|
||||
map $host $app_scheme_raw {
|
||||
default "${APP_SCHEME}";
|
||||
}
|
||||
|
||||
map $app_scheme_raw $force_https {
|
||||
"https" 1;
|
||||
"on" 1;
|
||||
"ssl" 1;
|
||||
"1" 1;
|
||||
"true" 1;
|
||||
"yes" 1;
|
||||
default 0;
|
||||
}
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
"" close;
|
||||
'' close;
|
||||
}
|
||||
|
||||
map $http_host $this_host {
|
||||
"" $host;
|
||||
"" $host;
|
||||
default $http_host;
|
||||
}
|
||||
|
||||
map $http_x_forwarded_proto $the_scheme {
|
||||
default $http_x_forwarded_proto;
|
||||
"" $scheme;
|
||||
}
|
||||
map $http_x_forwarded_host $the_host {
|
||||
"" $this_host;
|
||||
default $http_x_forwarded_host;
|
||||
"" $this_host;
|
||||
}
|
||||
|
||||
map $http_x_forwarded_proto $auto_scheme {
|
||||
"" $scheme;
|
||||
default $http_x_forwarded_proto;
|
||||
}
|
||||
|
||||
map $force_https $the_scheme {
|
||||
1 https;
|
||||
default $auto_scheme;
|
||||
}
|
||||
|
||||
upstream service {
|
||||
server php:20000 weight=5 max_fails=3 fail_timeout=30s;
|
||||
keepalive 16;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
|
||||
125
electron/lib/mcp.js
vendored
125
electron/lib/mcp.js
vendored
@ -4,7 +4,7 @@
|
||||
* DooTask 的 Electron 客户端集成了 Model Context Protocol (MCP) 服务,
|
||||
* 允许 AI 助手(如 Claude)直接与 DooTask 任务进行交互。
|
||||
*
|
||||
* 提供的工具(共 29 个):
|
||||
* 提供的工具(共 27 个):
|
||||
*
|
||||
* === 用户管理 ===
|
||||
* - get_users_basic - 批量获取用户基础信息(1-50个),便于匹配负责人/协助人
|
||||
@ -43,7 +43,6 @@
|
||||
* === 消息通知 ===
|
||||
* - search_dialogs - 按名称搜索群聊或联系人,返回 dialog_id/userid
|
||||
* - send_message - 发送消息到对话(支持 dialog_id 或 userid)
|
||||
* - send_task_ai_message - 以AI助手身份发送消息到任务对话,支持自定义发送者昵称
|
||||
* - get_message_list - 获取对话消息记录(支持 dialog_id 或 userid)
|
||||
*
|
||||
* === 智能搜索 ===
|
||||
@ -229,7 +228,7 @@ class DooTaskMCP {
|
||||
return { error: 'Result contains non-serializable data' };
|
||||
}
|
||||
} catch (error) {
|
||||
return { error: error.msg || error.message || String(error) || 'API request failed', ret: error.ret, data: error.data };
|
||||
return { error: error.msg || error.message || String(error) || 'API request failed' };
|
||||
}
|
||||
})()
|
||||
`);
|
||||
@ -243,10 +242,6 @@ class DooTaskMCP {
|
||||
const result = await Promise.race([executePromise, timeoutPromise]);
|
||||
|
||||
if (result && result.error) {
|
||||
// 多结束/开始状态(-4005/-4006):保留 ret 与 flow_items 交给工具处理,不直接抛错
|
||||
if (result.ret === -4005 || result.ret === -4006) {
|
||||
return result;
|
||||
}
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
@ -596,38 +591,14 @@ class DooTaskMCP {
|
||||
task_id: z.number()
|
||||
.min(1)
|
||||
.describe('要标记完成的任务ID'),
|
||||
flow_item_id: z.number()
|
||||
.optional()
|
||||
.describe('工作流状态ID'),
|
||||
}),
|
||||
execute: async (params) => {
|
||||
const now = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
const requestData = {
|
||||
const result = await this.request('POST', 'project/task/update', {
|
||||
task_id: params.task_id,
|
||||
complete_at: now,
|
||||
};
|
||||
if (params.flow_item_id) {
|
||||
requestData.flow_item_id = params.flow_item_id;
|
||||
}
|
||||
|
||||
const result = await this.request('POST', 'project/task/update', requestData);
|
||||
|
||||
// 处理多结束状态的情况
|
||||
if (result.ret === -4005) {
|
||||
const flowItems = result.data?.flow_items || [];
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
message: '存在多个结束状态,请选择要使用的状态后重新调用此工具,并指定flow_item_id参数',
|
||||
task_id: params.task_id,
|
||||
flow_items: flowItems,
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
@ -749,9 +720,6 @@ class DooTaskMCP {
|
||||
complete_at: z.union([z.string(), z.boolean()])
|
||||
.optional()
|
||||
.describe('完成时间。传时间字符串标记完成,传false标记未完成'),
|
||||
flow_item_id: z.number()
|
||||
.optional()
|
||||
.describe('工作流状态ID'),
|
||||
}),
|
||||
execute: async (params) => {
|
||||
const requestData = {
|
||||
@ -766,42 +734,9 @@ class DooTaskMCP {
|
||||
if (params.start_at !== undefined) requestData.start_at = params.start_at;
|
||||
if (params.end_at !== undefined) requestData.end_at = params.end_at;
|
||||
if (params.complete_at !== undefined) requestData.complete_at = params.complete_at;
|
||||
if (params.flow_item_id !== undefined) requestData.flow_item_id = params.flow_item_id;
|
||||
|
||||
const result = await this.request('POST', 'project/task/update', requestData);
|
||||
|
||||
// 处理多结束状态的情况(标记完成时)
|
||||
if (result.ret === -4005) {
|
||||
const flowItems = result.data?.flow_items || [];
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
message: '存在多个结束状态,请选择要使用的状态后重新调用此工具,并指定flow_item_id参数',
|
||||
task_id: params.task_id,
|
||||
flow_items: flowItems,
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// 处理多开始状态的情况(取消完成时)
|
||||
if (result.ret === -4006) {
|
||||
const flowItems = result.data?.flow_items || [];
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
message: '存在多个开始状态,请选择要使用的状态后重新调用此工具,并指定flow_item_id参数',
|
||||
task_id: params.task_id,
|
||||
flow_items: flowItems,
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
@ -1356,58 +1291,6 @@ class DooTaskMCP {
|
||||
}
|
||||
});
|
||||
|
||||
// 以AI助手身份发送消息到任务对话
|
||||
this.mcp.addTool({
|
||||
name: 'send_task_ai_message',
|
||||
description: '以AI助手身份发送消息到任务对话。应在每个重要里程碑、遇到阻塞、以及全部完成时主动调用。',
|
||||
parameters: z.object({
|
||||
task_id: z.number()
|
||||
.describe('目标任务ID'),
|
||||
text: z.string()
|
||||
.min(1)
|
||||
.describe('消息内容,支持 Markdown'),
|
||||
nickname: z.string()
|
||||
.max(20)
|
||||
.optional()
|
||||
.describe('自定义发送者昵称(最多20字),不传或留空时默认显示“AI 助手”'),
|
||||
silence: z.boolean()
|
||||
.optional()
|
||||
.describe('静默发送,不触发推送提醒'),
|
||||
}),
|
||||
execute: async (params) => {
|
||||
const payload = {
|
||||
task_id: params.task_id,
|
||||
text: params.text,
|
||||
text_type: 'md',
|
||||
};
|
||||
|
||||
if (params.nickname !== undefined) {
|
||||
payload.nickname = params.nickname;
|
||||
}
|
||||
|
||||
if (params.silence !== undefined) {
|
||||
payload.silence = params.silence ? 'yes' : 'no';
|
||||
}
|
||||
|
||||
const result = await this.request('POST', 'dialog/msg/send_ai_assistant', payload);
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
task_id: params.task_id,
|
||||
message: result.data,
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 获取对话消息列表
|
||||
this.mcp.addTool({
|
||||
name: 'get_message_list',
|
||||
|
||||
30
language/README.md
Normal file
30
language/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# 语言翻译工具说明
|
||||
|
||||
`language/translate.php` 脚本用于根据 `original-web.txt` 和 `original-api.txt` 中的内容,自动生成/更新 `translate.json` 以及前端使用的多语言文件。
|
||||
|
||||
## 使用步骤
|
||||
|
||||
1. 在项目根目录 `.env` 文件中配置:
|
||||
|
||||
```dotenv
|
||||
OPENAI_API_KEY=你的OpenAI密钥
|
||||
OPENAI_BASE_URL=可选的自定义API地址
|
||||
OPENAI_PROXY_URL=可选的代理地址
|
||||
```
|
||||
|
||||
2. 在 `language` 目录下执行:
|
||||
|
||||
```bash
|
||||
php translate.php
|
||||
```
|
||||
|
||||
3. 查看生成的翻译结果:
|
||||
|
||||
- 翻译详情:`language/translate.json`
|
||||
- API 文件:`public/language/api/*.json`
|
||||
- Web 文件:`public/language/web/*.js`
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 若 `.env` 未设置 `OPENAI_API_KEY`,脚本会直接退出。
|
||||
- `OPENAI_PROXY_URL` 可选,留空时不会设置代理。
|
||||
9
language/composer.json
Normal file
9
language/composer.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "dootask/language",
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"orhanerday/open-ai": "^5.2"
|
||||
}
|
||||
}
|
||||
82
language/composer.lock
generated
Normal file
82
language/composer.lock
generated
Normal file
@ -0,0 +1,82 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ec9d23d3c9171a27ef10589ff18aaf1d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "orhanerday/open-ai",
|
||||
"version": "5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/orhanerday/open-ai.git",
|
||||
"reference": "d8c78fe2f5fed59e0ba458f90b5589ed9f13a367"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/orhanerday/open-ai/zipball/d8c78fe2f5fed59e0ba458f90b5589ed9f13a367",
|
||||
"reference": "d8c78fe2f5fed59e0ba458f90b5589ed9f13a367",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"pestphp/pest": "^1.20",
|
||||
"spatie/ray": "^1.28"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Orhanerday\\OpenAi\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Orhan Erday",
|
||||
"email": "orhanerday@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "OpenAI GPT-3 Api Client in PHP",
|
||||
"homepage": "https://github.com/orhanerday/open-ai",
|
||||
"keywords": [
|
||||
"open-ai",
|
||||
"orhanerday"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/orhanerday/open-ai/issues",
|
||||
"source": "https://github.com/orhanerday/open-ai/tree/5.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/orhanerday",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-29T12:31:54+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.4",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
@ -976,7 +976,7 @@ LDAP 用户缺少邮箱属性,请联系管理员配置
|
||||
负责人不能任命为项目管理员
|
||||
普通成员不能移出群主或群管理员
|
||||
只有群主、群管理员或邀请人可以移出成员
|
||||
仅群主、项目/任务负责人或系统管理员可设置或取消他人待办
|
||||
仅群主、项目/任务负责人可设置或取消他人待办
|
||||
请选择文件
|
||||
仅支持 xls/xlsx/csv 文件
|
||||
文件中没有可导入的数据
|
||||
@ -992,7 +992,3 @@ LDAP 用户缺少邮箱属性,请联系管理员配置
|
||||
请选择成员
|
||||
待办提醒
|
||||
你有一条待办到提醒时间啦
|
||||
发送者昵称最多不能超过20字
|
||||
AI 助手
|
||||
没有查看权限
|
||||
当前仅指定人员可以创建项目
|
||||
|
||||
@ -1668,12 +1668,6 @@ WiFi签到延迟时长为±1分钟。
|
||||
|
||||
你确定将【(*)】设为管理员吗?
|
||||
你确定取消【(*)】管理员身份吗?
|
||||
你确定将【(*)】的邮箱标记为已认证吗?
|
||||
你确定将【(*)】的邮箱标记为未认证吗?
|
||||
标记邮箱为已认证
|
||||
标记邮箱为未认证
|
||||
标记选中(*)项为已认证
|
||||
标记选中(*)项为未认证
|
||||
|
||||
你确定要取消任务时间吗?
|
||||
更新子任务
|
||||
@ -2401,7 +2395,7 @@ AI任务分析
|
||||
部门管理员同步失败
|
||||
待办设置权限
|
||||
允许:所有成员可设置/取消他人待办。
|
||||
禁止:仅本人、系统管理员、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置/取消待办。
|
||||
禁止:仅本人、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置/取消待办。
|
||||
|
||||
批量导入用户
|
||||
请按模板填写后上传,列顺序:邮箱、昵称、初始密码、职位(选填);单次最多导入500条。
|
||||
@ -2440,17 +2434,3 @@ AI任务分析
|
||||
暂无完成
|
||||
取消提醒
|
||||
确定取消该成员的提醒时间吗?
|
||||
项目与任务
|
||||
暂无项目
|
||||
暂无任务
|
||||
负责
|
||||
协作
|
||||
成员
|
||||
(*)分钟前
|
||||
(*)小时前
|
||||
(*)天前
|
||||
所有人:所有成员均可创建项目。
|
||||
可创建项目的人员
|
||||
系统管理员(始终可创建,不受开关限制)。
|
||||
部门负责人与部门管理员。
|
||||
下方指定的人员。
|
||||
|
||||
@ -34583,6 +34583,18 @@
|
||||
"id": "Izinkan: Semua anggota dapat mengatur\/membatalkan tugas orang lain.",
|
||||
"ru": "Разрешить: все участники могут создавать\/отменять задачи для других."
|
||||
},
|
||||
{
|
||||
"key": "禁止:仅本人、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置\/取消待办。",
|
||||
"zh": "",
|
||||
"zh-CHT": "禁止:僅本人、群主(含群管理員)、專案負責人(含專案管理員)、任務負責人可設定\/取消待辦。",
|
||||
"en": "Prohibit: Only the user, group owner (including group admins), project owner (including project admins), and task owner can set\/cancel to-dos.",
|
||||
"ko": "금지: 본인, 그룹 소유자(그룹 관리자 포함), 프로젝트 책임자(프로젝트 관리자 포함), 작업 책임자만 할 일을 설정\/취소할 수 있습니다.",
|
||||
"ja": "禁止:本人、グループオーナー(グループ管理者を含む)、プロジェクト責任者(プロジェクト管理者を含む)、タスク責任者のみがToDoを設定\/取消できます。",
|
||||
"de": "Verbieten: Nur der Benutzer selbst, der Gruppeninhaber (einschließlich Gruppenadministratoren), der Projektverantwortliche (einschließlich Projektadministratoren) und der Aufgabenverantwortliche können Aufgaben festlegen\/abbrechen.",
|
||||
"fr": "Interdire : seuls l’utilisateur lui-même, le propriétaire du groupe (y compris les administrateurs du groupe), le responsable du projet (y compris les administrateurs du projet) et le responsable de la tâche peuvent définir\/annuler des tâches à faire.",
|
||||
"id": "Larang: Hanya pengguna sendiri, pemilik grup (termasuk admin grup), penanggung jawab proyek (termasuk admin proyek), dan penanggung jawab tugas yang dapat mengatur\/membatalkan tugas.",
|
||||
"ru": "Запретить: только сам пользователь, владелец группы (включая администраторов группы), ответственный за проект (включая администраторов проекта) и ответственный за задачу могут создавать\/отменять задачи."
|
||||
},
|
||||
{
|
||||
"key": "批量导入用户",
|
||||
"zh": "",
|
||||
@ -35015,6 +35027,18 @@
|
||||
"id": "Anda yakin ingin membatalkan waktu pengingat anggota ini?",
|
||||
"ru": "Вы уверены, что хотите отменить время напоминания для этого участника?"
|
||||
},
|
||||
{
|
||||
"key": "仅群主、项目\/任务负责人可设置或取消他人待办",
|
||||
"zh": "",
|
||||
"zh-CHT": "僅群主、專案\/任務負責人可設定或取消他人待辦",
|
||||
"en": "Only the group owner and project\/task owners can set or cancel others' to-dos",
|
||||
"ko": "그룹 소유자 및 프로젝트\/작업 담당자만 다른 사람의 할 일을 설정하거나 취소할 수 있습니다",
|
||||
"ja": "グループオーナーおよびプロジェクト\/タスクの責任者のみ、他のメンバーのToDoを設定またはキャンセルできます",
|
||||
"de": "Nur der Gruppeninhaber sowie Projekt-\/Aufgabenverantwortliche können To-dos anderer festlegen oder abbrechen",
|
||||
"fr": "Seuls le propriétaire du groupe et les responsables de projet\/tâche peuvent définir ou annuler les tâches à faire des autres",
|
||||
"id": "Hanya pemilik grup dan penanggung jawab proyek\/tugas yang dapat mengatur atau membatalkan tugas orang lain",
|
||||
"ru": "Только владелец группы и ответственные за проект\/задачу могут устанавливать или отменять задачи других пользователей"
|
||||
},
|
||||
{
|
||||
"key": "请选择文件",
|
||||
"zh": "",
|
||||
@ -35146,305 +35170,5 @@
|
||||
"fr": "Vous avez une tâche dont l’heure de rappel est arrivée",
|
||||
"id": "Anda memiliki satu tugas yang telah mencapai waktu pengingat",
|
||||
"ru": "У вас есть задача, для которой наступило время напоминания"
|
||||
},
|
||||
{
|
||||
"key": "你确定将【(%T1)】的邮箱标记为已认证吗?",
|
||||
"zh": "",
|
||||
"zh-CHT": "你確定將【(%T1)】的郵箱標記為已認證嗎?",
|
||||
"en": "Are you sure you want to mark the email address of 【(%T1)】 as verified?",
|
||||
"ko": "【(%T1)】의 이메일을 인증됨으로 표시하시겠습니까?",
|
||||
"ja": "【(%T1)】のメールアドレスを認証済みにしますか?",
|
||||
"de": "Möchten Sie die E-Mail-Adresse von 【(%T1)】 wirklich als verifiziert markieren?",
|
||||
"fr": "Voulez-vous vraiment marquer l’adresse e-mail de 【(%T1)】 comme vérifiée ?",
|
||||
"id": "Apakah Anda yakin ingin menandai email 【(%T1)】 sebagai terverifikasi?",
|
||||
"ru": "Вы уверены, что хотите отметить адрес электронной почты 【(%T1)】 как подтвержденный?"
|
||||
},
|
||||
{
|
||||
"key": "你确定将【(%T1)】的邮箱标记为未认证吗?",
|
||||
"zh": "",
|
||||
"zh-CHT": "你確定將【(%T1)】的郵箱標記為未認證嗎?",
|
||||
"en": "Are you sure you want to mark the email address of 【(%T1)】 as unverified?",
|
||||
"ko": "【(%T1)】의 이메일을 미인증으로 표시하시겠습니까?",
|
||||
"ja": "【(%T1)】のメールアドレスを未認証にしますか?",
|
||||
"de": "Möchten Sie die E-Mail-Adresse von 【(%T1)】 wirklich als nicht verifiziert markieren?",
|
||||
"fr": "Voulez-vous vraiment marquer l’adresse e-mail de 【(%T1)】 comme non vérifiée ?",
|
||||
"id": "Apakah Anda yakin ingin menandai email 【(%T1)】 sebagai belum terverifikasi?",
|
||||
"ru": "Вы уверены, что хотите отметить адрес электронной почты 【(%T1)】 как неподтвержденный?"
|
||||
},
|
||||
{
|
||||
"key": "标记邮箱为已认证",
|
||||
"zh": "",
|
||||
"zh-CHT": "標記郵箱為已認證",
|
||||
"en": "Mark email as verified",
|
||||
"ko": "이메일을 인증됨으로 표시",
|
||||
"ja": "メールアドレスを認証済みにする",
|
||||
"de": "E-Mail als verifiziert markieren",
|
||||
"fr": "Marquer l’adresse e-mail comme vérifiée",
|
||||
"id": "Tandai email sebagai terverifikasi",
|
||||
"ru": "Отметить адрес электронной почты как подтвержденный"
|
||||
},
|
||||
{
|
||||
"key": "标记邮箱为未认证",
|
||||
"zh": "",
|
||||
"zh-CHT": "標記郵箱為未認證",
|
||||
"en": "Mark email as unverified",
|
||||
"ko": "이메일을 미인증으로 표시",
|
||||
"ja": "メールアドレスを未認証にする",
|
||||
"de": "E-Mail als nicht verifiziert markieren",
|
||||
"fr": "Marquer l’adresse e-mail comme non vérifiée",
|
||||
"id": "Tandai email sebagai belum terverifikasi",
|
||||
"ru": "Отметить адрес электронной почты как неподтвержденный"
|
||||
},
|
||||
{
|
||||
"key": "标记选中(%T1)项为已认证",
|
||||
"zh": "",
|
||||
"zh-CHT": "標記選中(%T1)項為已認證",
|
||||
"en": "Mark the selected (%T1) items as verified",
|
||||
"ko": "선택한 (%T1)개 항목을 인증됨으로 표시",
|
||||
"ja": "選択した(%T1)件を認証済みにする",
|
||||
"de": "Ausgewählte (%T1) Elemente als verifiziert markieren",
|
||||
"fr": "Marquer les (%T1) éléments sélectionnés comme vérifiés",
|
||||
"id": "Tandai (%T1) item yang dipilih sebagai terverifikasi",
|
||||
"ru": "Отметить выбранные элементы (%T1) как подтвержденные"
|
||||
},
|
||||
{
|
||||
"key": "标记选中(%T1)项为未认证",
|
||||
"zh": "",
|
||||
"zh-CHT": "標記選中(%T1)項為未認證",
|
||||
"en": "Mark the selected (%T1) items as unverified",
|
||||
"ko": "선택한 (%T1)개 항목을 미인증으로 표시",
|
||||
"ja": "選択した(%T1)件を未認証にする",
|
||||
"de": "Ausgewählte (%T1) Elemente als nicht verifiziert markieren",
|
||||
"fr": "Marquer les (%T1) éléments sélectionnés comme non vérifiés",
|
||||
"id": "Tandai (%T1) item yang dipilih sebagai belum terverifikasi",
|
||||
"ru": "Отметить выбранные элементы (%T1) как неподтвержденные"
|
||||
},
|
||||
{
|
||||
"key": "禁止:仅本人、系统管理员、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置\/取消待办。",
|
||||
"zh": "",
|
||||
"zh-CHT": "禁止:僅本人、系統管理員、群主(含群管理員)、專案負責人(含專案管理員)、任務負責人可設定\/取消待辦。",
|
||||
"en": "Prohibited: Only the user themself, system administrators, group owners (including group administrators), project owners (including project administrators), and task owners can set\/cancel to-dos.",
|
||||
"ko": "금지: 본인, 시스템 관리자, 그룹 소유자(그룹 관리자 포함), 프로젝트 책임자(프로젝트 관리자 포함), 작업 책임자만 할 일을 설정\/취소할 수 있습니다.",
|
||||
"ja": "禁止:本人、システム管理者、グループオーナー(グループ管理者を含む)、プロジェクト責任者(プロジェクト管理者を含む)、タスク責任者のみがToDoを設定\/取消できます。",
|
||||
"de": "Unzulässig: Nur die Person selbst, Systemadministratoren, Gruppenbesitzer (einschließlich Gruppenadministratoren), Projektverantwortliche (einschließlich Projektadministratoren) und Aufgabenverantwortliche können To-dos festlegen\/abbrechen.",
|
||||
"fr": "Interdit : seuls l’utilisateur lui-même, les administrateurs système, les propriétaires de groupe (y compris les administrateurs de groupe), les responsables de projet (y compris les administrateurs de projet) et les responsables de tâche peuvent définir\/annuler des tâches à faire.",
|
||||
"id": "Dilarang: Hanya pengguna itu sendiri, administrator sistem, pemilik grup (termasuk administrator grup), penanggung jawab proyek (termasuk administrator proyek), dan penanggung jawab tugas yang dapat mengatur\/membatalkan to-do.",
|
||||
"ru": "Запрещено: только сам пользователь, системные администраторы, владельцы групп (включая администраторов групп), ответственные за проект (включая администраторов проекта) и ответственные за задачу могут устанавливать\/отменять задачи."
|
||||
},
|
||||
{
|
||||
"key": "仅群主、项目\/任务负责人或系统管理员可设置或取消他人待办",
|
||||
"zh": "",
|
||||
"zh-CHT": "僅群主、專案\/任務負責人或系統管理員可設定或取消他人待辦",
|
||||
"en": "Only group owners, project\/task owners, or system administrators can set or cancel others' to-dos",
|
||||
"ko": "그룹 소유자, 프로젝트\/작업 책임자 또는 시스템 관리자만 다른 사람의 할 일을 설정하거나 취소할 수 있습니다.",
|
||||
"ja": "グループオーナー、プロジェクト\/タスク責任者、またはシステム管理者のみが他人のToDoを設定または取消できます",
|
||||
"de": "Nur Gruppenbesitzer, Projekt-\/Aufgabenverantwortliche oder Systemadministratoren können To-dos anderer festlegen oder abbrechen",
|
||||
"fr": "Seuls les propriétaires de groupe, les responsables de projet\/tâche ou les administrateurs système peuvent définir ou annuler les tâches à faire d’autres personnes",
|
||||
"id": "Hanya pemilik grup, penanggung jawab proyek\/tugas, atau administrator sistem yang dapat mengatur atau membatalkan to-do orang lain",
|
||||
"ru": "Только владельцы групп, ответственные за проект\/задачу или системные администраторы могут устанавливать или отменять задачи других пользователей"
|
||||
},
|
||||
{
|
||||
"key": "发送者昵称最多不能超过20字",
|
||||
"zh": "",
|
||||
"zh-CHT": "發送者暱稱最多不能超過20字",
|
||||
"en": "The sender nickname cannot exceed 20 characters",
|
||||
"ko": "발신자 닉네임은 최대 20자를 초과할 수 없습니다.",
|
||||
"ja": "送信者のニックネームは20文字を超えることはできません",
|
||||
"de": "Der Spitzname des Absenders darf höchstens 20 Zeichen lang sein",
|
||||
"fr": "Le pseudonyme de l’expéditeur ne peut pas dépasser 20 caractères",
|
||||
"id": "Nama panggilan pengirim tidak boleh melebihi 20 karakter",
|
||||
"ru": "Псевдоним отправителя не может превышать 20 символов"
|
||||
},
|
||||
{
|
||||
"key": "项目与任务",
|
||||
"zh": "",
|
||||
"zh-CHT": "項目與任務",
|
||||
"en": "Projects and Tasks",
|
||||
"ko": "프로젝트 및 작업",
|
||||
"ja": "プロジェクトとタスク",
|
||||
"de": "Projekte und Aufgaben",
|
||||
"fr": "Projets et tâches",
|
||||
"id": "Proyek dan Tugas",
|
||||
"ru": "Проекты и задачи"
|
||||
},
|
||||
{
|
||||
"key": "暂无项目",
|
||||
"zh": "",
|
||||
"zh-CHT": "暫無項目",
|
||||
"en": "No projects yet",
|
||||
"ko": "프로젝트가 없습니다",
|
||||
"ja": "プロジェクトがありません",
|
||||
"de": "Noch keine Projekte",
|
||||
"fr": "Aucun projet",
|
||||
"id": "Belum ada proyek",
|
||||
"ru": "Пока нет проектов"
|
||||
},
|
||||
{
|
||||
"key": "暂无任务",
|
||||
"zh": "",
|
||||
"zh-CHT": "暫無任務",
|
||||
"en": "No tasks yet",
|
||||
"ko": "작업이 없습니다",
|
||||
"ja": "タスクがありません",
|
||||
"de": "Noch keine Aufgaben",
|
||||
"fr": "Aucune tâche",
|
||||
"id": "Belum ada tugas",
|
||||
"ru": "Пока нет задач"
|
||||
},
|
||||
{
|
||||
"key": "负责",
|
||||
"zh": "",
|
||||
"zh-CHT": "負責",
|
||||
"en": "Responsible",
|
||||
"ko": "담당",
|
||||
"ja": "担当",
|
||||
"de": "Verantwortlich",
|
||||
"fr": "Responsable",
|
||||
"id": "Penanggung jawab",
|
||||
"ru": "Ответственный"
|
||||
},
|
||||
{
|
||||
"key": "协作",
|
||||
"zh": "",
|
||||
"zh-CHT": "協作",
|
||||
"en": "Collaborate",
|
||||
"ko": "협업",
|
||||
"ja": "コラボレーション",
|
||||
"de": "Zusammenarbeit",
|
||||
"fr": "Collaboration",
|
||||
"id": "Kolaborasi",
|
||||
"ru": "Совместная работа"
|
||||
},
|
||||
{
|
||||
"key": "成员",
|
||||
"zh": "",
|
||||
"zh-CHT": "成員",
|
||||
"en": "Members",
|
||||
"ko": "구성원",
|
||||
"ja": "メンバー",
|
||||
"de": "Mitglieder",
|
||||
"fr": "Membres",
|
||||
"id": "Anggota",
|
||||
"ru": "Участники"
|
||||
},
|
||||
{
|
||||
"key": "(%T1)分钟前",
|
||||
"zh": "",
|
||||
"zh-CHT": "(%T1)分鐘前",
|
||||
"en": "(%T1) minutes ago",
|
||||
"ko": "(%T1)분 전",
|
||||
"ja": "(%T1)分前",
|
||||
"de": "vor (%T1) Minuten",
|
||||
"fr": "il y a (%T1) minutes",
|
||||
"id": "(%T1) menit yang lalu",
|
||||
"ru": "(%T1) минут назад"
|
||||
},
|
||||
{
|
||||
"key": "(%T1)小时前",
|
||||
"zh": "",
|
||||
"zh-CHT": "(%T1)小時前",
|
||||
"en": "(%T1) hours ago",
|
||||
"ko": "(%T1)시간 전",
|
||||
"ja": "(%T1)時間前",
|
||||
"de": "vor (%T1) Stunden",
|
||||
"fr": "il y a (%T1) heures",
|
||||
"id": "(%T1) jam yang lalu",
|
||||
"ru": "(%T1) часов назад"
|
||||
},
|
||||
{
|
||||
"key": "(%T1)天前",
|
||||
"zh": "",
|
||||
"zh-CHT": "(%T1)天前",
|
||||
"en": "(%T1) days ago",
|
||||
"ko": "(%T1)일 전",
|
||||
"ja": "(%T1)日前",
|
||||
"de": "vor (%T1) Tagen",
|
||||
"fr": "il y a (%T1) jours",
|
||||
"id": "(%T1) hari yang lalu",
|
||||
"ru": "(%T1) дней назад"
|
||||
},
|
||||
{
|
||||
"key": "所有人:所有成员均可创建项目。",
|
||||
"zh": "",
|
||||
"zh-CHT": "所有人:所有成員均可創建項目。",
|
||||
"en": "Everyone: All members can create projects.",
|
||||
"ko": "모든 사람: 모든 구성원이 프로젝트를 만들 수 있습니다.",
|
||||
"ja": "全員:すべてのメンバーがプロジェクトを作成できます。",
|
||||
"de": "Alle: Alle Mitglieder können Projekte erstellen.",
|
||||
"fr": "Tout le monde : tous les membres peuvent créer des projets.",
|
||||
"id": "Semua orang: Semua anggota dapat membuat proyek.",
|
||||
"ru": "Все: все участники могут создавать проекты."
|
||||
},
|
||||
{
|
||||
"key": "可创建项目的人员",
|
||||
"zh": "",
|
||||
"zh-CHT": "可創建項目的人員",
|
||||
"en": "Who can create projects",
|
||||
"ko": "프로젝트를 만들 수 있는 사람",
|
||||
"ja": "プロジェクトを作成できるユーザー",
|
||||
"de": "Wer Projekte erstellen kann",
|
||||
"fr": "Personnes pouvant créer des projets",
|
||||
"id": "Siapa yang dapat membuat proyek",
|
||||
"ru": "Кто может создавать проекты"
|
||||
},
|
||||
{
|
||||
"key": "系统管理员(始终可创建,不受开关限制)。",
|
||||
"zh": "",
|
||||
"zh-CHT": "系統管理員(始終可創建,不受開關限制)。",
|
||||
"en": "System administrators (can always create, not subject to the toggle).",
|
||||
"ko": "시스템 관리자 (항상 생성 가능하며 설정 제한을 받지 않습니다).",
|
||||
"ja": "システム管理者(常に作成可能で、設定の制限を受けません)。",
|
||||
"de": "Systemadministratoren (können immer erstellen, unabhängig von der Einstellung).",
|
||||
"fr": "Administrateurs système (peuvent toujours créer, sans restriction de l'option).",
|
||||
"id": "Administrator sistem (selalu dapat membuat, tidak dibatasi oleh pengaturan).",
|
||||
"ru": "Системные администраторы (всегда могут создавать, без ограничений переключателя)."
|
||||
},
|
||||
{
|
||||
"key": "部门负责人与部门管理员。",
|
||||
"zh": "",
|
||||
"zh-CHT": "部門負責人與部門管理員。",
|
||||
"en": "Department heads and department administrators.",
|
||||
"ko": "부서 책임자 및 부서 관리자.",
|
||||
"ja": "部門責任者と部門管理者。",
|
||||
"de": "Abteilungsleiter und Abteilungsadministratoren.",
|
||||
"fr": "Responsables de département et administrateurs de département.",
|
||||
"id": "Kepala departemen dan administrator departemen.",
|
||||
"ru": "Руководители отделов и администраторы отделов."
|
||||
},
|
||||
{
|
||||
"key": "下方指定的人员。",
|
||||
"zh": "",
|
||||
"zh-CHT": "下方指定的人員。",
|
||||
"en": "The people specified below.",
|
||||
"ko": "아래에 지정된 사람.",
|
||||
"ja": "以下で指定されたユーザー。",
|
||||
"de": "Die unten angegebenen Personen.",
|
||||
"fr": "Les personnes spécifiées ci-dessous.",
|
||||
"id": "Orang yang ditentukan di bawah.",
|
||||
"ru": "Указанные ниже люди."
|
||||
},
|
||||
{
|
||||
"key": "没有查看权限",
|
||||
"zh": "",
|
||||
"zh-CHT": "沒有查看權限",
|
||||
"en": "No view permission",
|
||||
"ko": "보기 권한이 없습니다",
|
||||
"ja": "閲覧権限がありません",
|
||||
"de": "Keine Anzeigeberechtigung",
|
||||
"fr": "Aucune autorisation de consultation",
|
||||
"id": "Tidak ada izin lihat",
|
||||
"ru": "Нет прав на просмотр"
|
||||
},
|
||||
{
|
||||
"key": "当前仅指定人员可以创建项目",
|
||||
"zh": "",
|
||||
"zh-CHT": "當前僅指定人員可以創建項目",
|
||||
"en": "Currently only specified people can create projects",
|
||||
"ko": "현재 지정된 사람만 프로젝트를 만들 수 있습니다",
|
||||
"ja": "現在、指定されたユーザーのみがプロジェクトを作成できます",
|
||||
"de": "Derzeit können nur bestimmte Personen Projekte erstellen",
|
||||
"fr": "Actuellement, seules les personnes spécifiées peuvent créer des projets",
|
||||
"id": "Saat ini hanya orang yang ditentukan yang dapat membuat proyek",
|
||||
"ru": "В настоящее время только указанные люди могут создавать проекты"
|
||||
}
|
||||
]
|
||||
352
language/translate.php
Executable file
352
language/translate.php
Executable file
@ -0,0 +1,352 @@
|
||||
<?php
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Orhanerday\OpenAi\OpenAi;
|
||||
|
||||
// 读取 .env 文件的简单工具函数
|
||||
function language_parse_env_file(string $path): array
|
||||
{
|
||||
$env = [];
|
||||
$lines = @file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
if ($lines === false) {
|
||||
return $env;
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$delimiterPosition = strpos($line, '=');
|
||||
if ($delimiterPosition === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = trim(substr($line, 0, $delimiterPosition));
|
||||
if (strpos($name, 'export ') === 0) {
|
||||
$name = trim(substr($name, 7));
|
||||
}
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = trim(substr($line, $delimiterPosition + 1));
|
||||
$length = strlen($value);
|
||||
if ($length >= 2) {
|
||||
$first = $value[0];
|
||||
$last = $value[$length - 1];
|
||||
if (($first === '"' && $last === '"') || ($first === "'" && $last === "'")) {
|
||||
$value = substr($value, 1, $length - 2);
|
||||
}
|
||||
}
|
||||
|
||||
$env[$name] = $value;
|
||||
}
|
||||
|
||||
return $env;
|
||||
}
|
||||
|
||||
// 获取环境变量值的简单工具函数
|
||||
function language_env_value(string $key, array $env): ?string
|
||||
{
|
||||
if (array_key_exists($key, $env)) {
|
||||
return $env[$key];
|
||||
}
|
||||
|
||||
$value = getenv($key);
|
||||
if ($value !== false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 读取语言环境配置
|
||||
$languageEnvFile = dirname(__DIR__) . '/.env';
|
||||
$languageEnv = is_readable($languageEnvFile) ? language_parse_env_file($languageEnvFile) : [];
|
||||
|
||||
// 优先从 .env 读取 OPENAI 配置,未找到时再次尝试 getenv 覆盖
|
||||
$openAiKey = trim(language_env_value('OPENAI_API_KEY', $languageEnv) ?? '');
|
||||
if ($openAiKey === '') {
|
||||
fwrite(STDERR, "OPENAI_API_KEY 未设置,请在项目根目录的 .env 中配置。\n");
|
||||
exit(1);
|
||||
}
|
||||
$openAiProxy = trim(language_env_value('OPENAI_PROXY_URL', $languageEnv) ?? '');
|
||||
$openAiBaseUrl = trim(language_env_value('OPENAI_BASE_URL', $languageEnv) ?? '');
|
||||
$openAiModel = trim(language_env_value('OPENAI_API_MODEL', $languageEnv) ?? '');
|
||||
|
||||
// 读取所有要翻译的内容
|
||||
$originals = [];
|
||||
$generateds = [];
|
||||
foreach (['web', 'api'] as $type) {
|
||||
$content = file_exists("original-{$type}.txt") ? file_get_contents("original-{$type}.txt") : "";
|
||||
$array = array_values(array_filter(array_unique(explode("\n", $content))));
|
||||
$generateds[$type] = $array;
|
||||
$originals = array_merge($originals, $array);
|
||||
}
|
||||
|
||||
// 判定是否存在translate.json文件
|
||||
if (!file_exists("translate.json")) {
|
||||
print_r("translate.json not exists");
|
||||
exit;
|
||||
}
|
||||
|
||||
$translations = []; // 翻译数据
|
||||
$regrror = []; // 正则匹配错误的数据
|
||||
$redundants = []; // 多余的数据
|
||||
$needs = []; // 需要翻译的数据
|
||||
|
||||
// 读取翻译数据
|
||||
$tmps = json_decode(file_get_contents("translate.json"), true);
|
||||
foreach ($tmps as $obj) {
|
||||
if (!isset($obj['key'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentKey = $obj['key'];
|
||||
$originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $currentKey);
|
||||
$translations[$originalKey] = $obj;
|
||||
|
||||
if (!in_array($originalKey, $originals)) {
|
||||
unset($translations[$originalKey]);
|
||||
$redundants[$originalKey] = $obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match_all('/\(%[TM]\d+\)/', $currentKey, $matches)) {
|
||||
foreach ($matches[0] as $match) {
|
||||
foreach ($obj as $k => $v) {
|
||||
if (empty($v)) {
|
||||
continue;
|
||||
}
|
||||
if (!str_contains($v, $match)) {
|
||||
// 正则匹配错误
|
||||
$regrror[$originalKey] = [
|
||||
$k => $v,
|
||||
'match' => $match,
|
||||
'key' => $currentKey,
|
||||
];
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($regrror) > 0) {
|
||||
print_r("正则匹配错误的数据:\n");
|
||||
print_r($regrror);
|
||||
exit();
|
||||
}
|
||||
if (count($redundants) > 0) {
|
||||
print_r("多余的数据:\n");
|
||||
print_r(implode(", ", array_keys($redundants)) . "\n\n");
|
||||
}
|
||||
|
||||
// 需要翻译的数据
|
||||
foreach ($originals as $text) {
|
||||
$key = trim($text);
|
||||
if (!isset($translations[$key])) {
|
||||
$needs[$key] = $key;
|
||||
}
|
||||
}
|
||||
if (count($needs) > 0) {
|
||||
$array = array_chunk($needs, 10, true);
|
||||
$success = [];
|
||||
$error = [];
|
||||
$done = 0;
|
||||
foreach ($array as $index => $keys) {
|
||||
// 生成翻译内容
|
||||
foreach ($keys as &$key) {
|
||||
$c = 1;
|
||||
$key = preg_replace_callback('/\((\*+)\)/', function ($m) use (&$c) {
|
||||
$label = strlen($m[1]) > 1 ? "M" : "T";
|
||||
return "(%" . $label . $c++ . ")";
|
||||
}, $key);
|
||||
}
|
||||
$content = implode("\n", $keys);
|
||||
|
||||
// 开始翻译
|
||||
print_r("正在翻译:" . (count($keys) + $done) . "/" . count($needs) . "...\n");
|
||||
$openAi = new OpenAi($openAiKey);
|
||||
if ($openAiBaseUrl !== '') {
|
||||
$openAi->setBaseURL(rtrim(preg_replace('#/v\d+/?$#', '', $openAiBaseUrl), '/'));
|
||||
}
|
||||
if ($openAiProxy !== '') {
|
||||
$openAi->setProxy($openAiProxy);
|
||||
}
|
||||
$result = $openAi->chat([
|
||||
"model" => $openAiModel,
|
||||
"reasoning_effort" => "low",
|
||||
'messages' => [
|
||||
[
|
||||
"role" => "system",
|
||||
"content" => <<<EOF
|
||||
你是一个专业的翻译器,翻译的结果尽量符合 “项目任务管理系统” 的使用,请将提供的内容按每行一个翻译成:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"key": "", // 原文本
|
||||
"zh": "", // 留空(不用翻译)
|
||||
"zh-CHT": "", // 繁体中文
|
||||
"en": "", // 英语
|
||||
"ko": "", // 韩语
|
||||
"ja": "", // 日语
|
||||
"de": "", // 德语
|
||||
"fr": "", // 法语
|
||||
"id": "", // 印度尼西亚语
|
||||
"ru": "" // 俄语
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
请注意:(%T1)、(%T2)、(%T3)、(%M1)、(%M2) ...... 这类以 `小括号(%+内容)` 的字符组合是一个变量,翻译时请保留。
|
||||
|
||||
例子1:
|
||||
原文:此(%T1)已经处于【(%T2)】共享文件夹中,无法重复共享。
|
||||
翻译成英语:This (%T1) is already in the 【(%T2)】 shared folder and cannot be shared again。
|
||||
|
||||
例子2:
|
||||
原文:(%T1)的周报[(%T2)][(%T3)月第(%T4)周]
|
||||
翻译成英语:Weekly report of (%T1) [(%T2)] [(Week (%T4) of (%T3) month)]
|
||||
|
||||
例子3:
|
||||
原文:(%T1)提交的「(%M2)」待你审批
|
||||
翻译成英语:'(%M2)' submitted by (%T1) is waiting for your approval
|
||||
|
||||
例子4:
|
||||
原文:您发起的「(%M1)」已通过
|
||||
翻译成英语:The '(%M1)' you initiated has been approved
|
||||
EOF,
|
||||
],
|
||||
[
|
||||
"role" => "user",
|
||||
"content" => $content,
|
||||
],
|
||||
]
|
||||
]);
|
||||
|
||||
// 处理结果
|
||||
$obj = json_decode($result);
|
||||
$txt = preg_replace('/(^\s*```json\s*|\s*```\s*$)/', "", $obj->choices[0]->message->content);
|
||||
$txt = preg_replace('/\(%([TM]\d+)\)/', '(%$1)', $txt);
|
||||
$arr = json_decode($txt, true);
|
||||
if (!$arr || !is_array($arr)) {
|
||||
$error = array_merge($error, array_flip($keys));
|
||||
print_r("翻译失败:\n" . $content . "\n\n");
|
||||
file_put_contents("translate-gpt.log", json_encode($obj, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n", FILE_APPEND);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 验证结果
|
||||
foreach ($arr as $item) {
|
||||
if (empty($item['key'])) {
|
||||
print_r("翻译结果不符合规范:key为空。\n");
|
||||
print_r($item);
|
||||
continue;
|
||||
}
|
||||
foreach (['key', 'zh', 'zh-CHT', 'en', 'ko', 'ja', 'de', 'fr', 'id', 'ru'] as $lang) {
|
||||
if (!isset($item[$lang])) {
|
||||
print_r("翻译结果不符合规范:{$item['key']},缺少:{$lang} 的值。\n");
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$currentKey = $item['key'];
|
||||
$originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $currentKey);
|
||||
if (preg_match_all('/\(%[TM]\d+\)/', $currentKey, $matches)) {
|
||||
foreach ($matches[0] as $match) {
|
||||
foreach ($item as $k => $v) {
|
||||
if (empty($v)) {
|
||||
continue;
|
||||
}
|
||||
if (!str_contains($v, $match)) {
|
||||
// 正则匹配错误
|
||||
$error[$originalKey] = [
|
||||
'key' => $currentKey,
|
||||
$k => $v,
|
||||
'match' => $match,
|
||||
];
|
||||
continue 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$item['zh'] = "";
|
||||
$translations[$originalKey] = $item;
|
||||
$success[$originalKey] = $item;
|
||||
}
|
||||
print_r("翻译完成:" . (count($keys) + $done) . "/" . count($needs) . "\n\n");
|
||||
$done += count($keys);
|
||||
}
|
||||
|
||||
if (count($error) > 0) {
|
||||
print_r("正则匹配错误的数据:\n");
|
||||
print_r(json_encode(array_values($error), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n");
|
||||
}
|
||||
|
||||
// 保存翻译结果
|
||||
file_put_contents("translate.json", json_encode(array_values($translations), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
print_r("----------------\n\n");
|
||||
print_r("总翻译:" . count($needs) . " 条\n");
|
||||
print_r("成功:" . count($success) . " 条\n");
|
||||
print_r("错误:" . count($error) . " 条\n\n");
|
||||
print_r("----------------\n\n");
|
||||
}
|
||||
|
||||
// 生成前端使用的文件
|
||||
foreach ($generateds as $type => $array) {
|
||||
$datas = [];
|
||||
foreach ($array as $text) {
|
||||
$text = trim($text);
|
||||
if (isset($translations[$text])) {
|
||||
$datas[] = $translations[$text];
|
||||
}
|
||||
}
|
||||
// 按长度排序
|
||||
$inOrder = [];
|
||||
foreach ($datas as $index => $item) {
|
||||
if (preg_match('/\(%[TM]\d+\)/', $item['key'])) {
|
||||
$inOrder[$index] = strlen($item['key']);
|
||||
} else {
|
||||
$inOrder[$index] = strlen($item['key']) + 10000000000;
|
||||
}
|
||||
}
|
||||
array_multisort($inOrder, SORT_DESC, $datas);
|
||||
// 合成数组
|
||||
$results = [];
|
||||
$index = 0;
|
||||
foreach ($datas as $items) {
|
||||
foreach ($items as $kk => $item) {
|
||||
if (!isset($results)) {
|
||||
$results[$kk] = [];
|
||||
}
|
||||
$results[$kk][] = $item;
|
||||
}
|
||||
}
|
||||
// 生成文件
|
||||
if ($type === 'api') {
|
||||
if (!is_dir("../public/language/api")) {
|
||||
mkdir("../public/language/api", 0777, true);
|
||||
}
|
||||
foreach ($results as $kk => $item) {
|
||||
$file = "../public/language/api/$kk.json";
|
||||
file_put_contents($file, json_encode($item, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
} elseif ($type === 'web') {
|
||||
if (!is_dir("../public/language/web")) {
|
||||
mkdir("../public/language/web", 0777, true);
|
||||
}
|
||||
foreach ($results as $kk => $item) {
|
||||
$file = "../public/language/web/$kk.js";
|
||||
file_put_contents($file, "if(typeof window.LANGUAGE_DATA===\"undefined\")window.LANGUAGE_DATA={};window.LANGUAGE_DATA[\"{$kk}\"]=" . json_encode($item, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
print_r("[$type] total: " . count($results['key']) . "\n");
|
||||
}
|
||||
|
||||
print_r("\n任务结束\n");
|
||||
@ -1,11 +1,13 @@
|
||||
{
|
||||
"name": "DooTask",
|
||||
"version": "1.7.90",
|
||||
"codeVerson": 237,
|
||||
"version": "1.7.67",
|
||||
"codeVerson": 235,
|
||||
"description": "DooTask is task management system.",
|
||||
"scripts": {
|
||||
"start": "./cmd dev",
|
||||
"build": "./cmd prod"
|
||||
"build": "./cmd prod",
|
||||
"version": "node ./bin/version.js",
|
||||
"translate": "./cmd translate"
|
||||
},
|
||||
"author": {
|
||||
"name": "KuaiFan",
|
||||
|
||||
@ -1 +1 @@
|
||||
import{n as m}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var p=function(){var t=this,r=t.$createElement;return t._self._c,t._m(0)},e=[function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"page-404"},[i("div",{staticClass:"flex-center position-ref full-height"},[i("div",{staticClass:"code"},[t._v("404")]),i("div",{staticClass:"message"},[t._v("Not Found")])])])}];const s={},o={};var _=m(s,p,e,!1,n,"7d7154a8",null,null);function n(t){for(let r in o)this[r]=o[r]}var it=function(){return _.exports}();export{it as default};
|
||||
import{n as m}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var p=function(){var t=this,r=t.$createElement;return t._self._c,t._m(0)},e=[function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"page-404"},[i("div",{staticClass:"flex-center position-ref full-height"},[i("div",{staticClass:"code"},[t._v("404")]),i("div",{staticClass:"message"},[t._v("Not Found")])])])}];const s={},o={};var _=m(s,p,e,!1,n,"7d7154a8",null,null);function n(t){for(let r in o)this[r]=o[r]}var it=function(){return _.exports}();export{it as default};
|
||||
File diff suppressed because one or more lines are too long
1
public/js/build/CheckinExport.6b9a9b8f.js
vendored
1
public/js/build/CheckinExport.6b9a9b8f.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/build/CheckinExport.935342d7.js
vendored
Normal file
1
public/js/build/CheckinExport.935342d7.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
.checkin-field .ivu-form-item-label{color:#f90;font-weight:500}.checkin-mac-header[data-v-eb58f07c]{margin-bottom:8px;font-weight:500;color:#606266}.checkin-mac-item[data-v-eb58f07c]{margin-bottom:8px}.checkin-mac-item .ivu-col[data-v-eb58f07c]{padding-right:8px}.checkin-mac-item .ivu-col[data-v-eb58f07c]:last-child{padding-right:0}.checkin-mac-del[data-v-eb58f07c]{display:flex;align-items:center;justify-content:center;cursor:pointer;color:red}.checkin-mac-del[data-v-eb58f07c]:hover{opacity:.8}.form-tip[data-v-eb58f07c]{font-size:12px;color:#999;margin-top:4px}.user-tags-preview[data-v-eb58f07c]{display:flex;align-items:center;flex-wrap:wrap;gap:8px;min-height:32px}.user-tags-preview .tag-pill[data-v-eb58f07c]{cursor:pointer;padding:6px 12px;border-radius:12px;font-size:13px;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:#f5f5f5;color:#606266;line-height:14px;height:26px;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.user-tags-preview .tag-pill.is-recognized[data-v-eb58f07c]{color:#67c23a}.user-tags-preview .tag-pill span[data-v-eb58f07c]{padding-left:8px;position:relative}.user-tags-preview .tag-pill span[data-v-eb58f07c]:before{content:"";position:absolute;left:2px;top:50%;transform:translateY(-50%);width:2px;height:2px;border-radius:50%;background-color:currentColor}.user-tags-preview .tags-empty[data-v-eb58f07c]{color:#909399}.user-tags-preview .tags-total[data-v-eb58f07c]{color:#909399;font-size:12px}.user-tags-preview .manage-button[data-v-eb58f07c]{margin-left:auto;display:inline-flex;align-items:center;gap:4px}.import-user-modal .import-tip[data-v-9d8f7ae8]{color:#808695;margin-bottom:12px}.import-user-modal .import-actions[data-v-9d8f7ae8]{display:flex;gap:12px;align-items:center}.import-user-modal .import-option[data-v-9d8f7ae8]{margin-top:12px}.import-user-modal .import-batch-label[data-v-9d8f7ae8]{flex-shrink:0;min-width:64px;color:#515a6e}.import-user-modal .import-setdept[data-v-9d8f7ae8]{display:flex;align-items:center;gap:8px;margin-top:12px}.import-user-modal .import-setdept .import-setdept-select[data-v-9d8f7ae8]{width:auto}.import-user-modal .import-setverity[data-v-9d8f7ae8]{display:flex;align-items:center;gap:8px;margin-top:12px}.import-user-modal .import-preview[data-v-9d8f7ae8],.import-user-modal .import-result[data-v-9d8f7ae8]{margin-top:16px}.import-user-modal[data-v-9d8f7ae8] .ivu-table-cell{white-space:nowrap}.import-user-modal[data-v-9d8f7ae8] .pwd-cell{cursor:pointer;letter-spacing:1px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.import-user-modal[data-v-9d8f7ae8] .pwd-cell:hover{color:#2d8cf0}.import-user-modal[data-v-9d8f7ae8] .import-row-error td{background-color:#fff2f0}
|
||||
.checkin-field .ivu-form-item-label{color:#f90;font-weight:500}.checkin-mac-header[data-v-eb58f07c]{margin-bottom:8px;font-weight:500;color:#606266}.checkin-mac-item[data-v-eb58f07c]{margin-bottom:8px}.checkin-mac-item .ivu-col[data-v-eb58f07c]{padding-right:8px}.checkin-mac-item .ivu-col[data-v-eb58f07c]:last-child{padding-right:0}.checkin-mac-del[data-v-eb58f07c]{display:flex;align-items:center;justify-content:center;cursor:pointer;color:red}.checkin-mac-del[data-v-eb58f07c]:hover{opacity:.8}.form-tip[data-v-eb58f07c]{font-size:12px;color:#999;margin-top:4px}.user-tags-preview[data-v-eb58f07c]{display:flex;align-items:center;flex-wrap:wrap;gap:8px;min-height:32px}.user-tags-preview .tag-pill[data-v-eb58f07c]{cursor:pointer;padding:6px 12px;border-radius:12px;font-size:13px;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:#f5f5f5;color:#606266;line-height:14px;height:26px;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.user-tags-preview .tag-pill.is-recognized[data-v-eb58f07c]{color:#67c23a}.user-tags-preview .tag-pill span[data-v-eb58f07c]{padding-left:8px;position:relative}.user-tags-preview .tag-pill span[data-v-eb58f07c]:before{content:"";position:absolute;left:2px;top:50%;transform:translateY(-50%);width:2px;height:2px;border-radius:50%;background-color:currentColor}.user-tags-preview .tags-empty[data-v-eb58f07c]{color:#909399}.user-tags-preview .tags-total[data-v-eb58f07c]{color:#909399;font-size:12px}.user-tags-preview .manage-button[data-v-eb58f07c]{margin-left:auto;display:inline-flex;align-items:center;gap:4px}.import-user-modal .import-tip[data-v-689116c0]{color:#808695;margin-bottom:12px}.import-user-modal .import-actions[data-v-689116c0]{display:flex;gap:12px;align-items:center}.import-user-modal .import-option[data-v-689116c0]{margin-top:12px}.import-user-modal .import-setdept[data-v-689116c0]{display:flex;align-items:flex-start;gap:8px;margin-top:12px}.import-user-modal .import-setdept .import-setdept-select[data-v-689116c0]{width:auto}.import-user-modal .import-preview[data-v-689116c0],.import-user-modal .import-result[data-v-689116c0]{margin-top:16px}.import-user-modal[data-v-689116c0] .ivu-table-cell{white-space:nowrap}.import-user-modal[data-v-689116c0] .pwd-cell{cursor:pointer;letter-spacing:1px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.import-user-modal[data-v-689116c0] .pwd-cell:hover{color:#2d8cf0}.import-user-modal[data-v-689116c0] .import-row-error td{background-color:#fff2f0}
|
||||
@ -1 +1 @@
|
||||
import{m as i}from"./vuex.cc7cb26e.js";import{n as o}from"./app.27305e7a.js";var d=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("Modal",{attrs:{value:t.value,title:t.$L("\u8D1F\u8D23\u4EBA\u89C6\u89D2"),"mask-closable":!1,width:"520"},on:{input:function(s){return t.$emit("input",s)}}},[e("div",{staticClass:"department-owner-view-modal"},[e("Alert",{attrs:{type:"info","show-icon":""}},[t._v(" "+t._s(t.$L("\u53EF\u67E5\u770B\u6240\u9009\u90E8\u95E8\u53CA\u6240\u6709\u4E0B\u7EA7\u90E8\u95E8\u6210\u5458\u53C2\u4E0E\u7684\u9879\u76EE\u548C\u4EFB\u52A1\uFF0C\u4EC5\u652F\u6301\u53EA\u8BFB\u67E5\u770B\u3002"))+" ")]),t.managedDepartments.length>1?e("div",{staticClass:"department-owner-view-actions"},[e("a",{attrs:{href:"javascript:void(0)"},on:{click:function(s){t.draftIds=[]}}},[t._v(t._s(t.$L("\u6E05\u7A7A")))]),e("a",{attrs:{href:"javascript:void(0)"},on:{click:function(s){t.draftIds=t.managedDepartments.map(function(n){return n.id})}}},[t._v(t._s(t.$L("\u5168\u9009")))]),e("a",{attrs:{href:"javascript:void(0)"},on:{click:t.reverseDraft}},[t._v(t._s(t.$L("\u53CD\u9009")))])]):t._e(),e("CheckboxGroup",{staticClass:"department-owner-view-list",model:{value:t.draftIds,callback:function(s){t.draftIds=s},expression:"draftIds"}},t._l(t.managedDepartments,function(s){return e("div",{key:s.id,class:["department-owner-view-item",t.draftIds.includes(s.id)?"active":""],on:{click:function(n){return t.toggleDraft(s.id)}}},[e("div",{staticClass:"department-owner-view-icon"},[e("i",{staticClass:"taskfont"},[t._v("\uE75C")])]),e("div",{staticClass:"department-owner-view-name"},[t._v(t._s(s.name))]),e("Checkbox",{staticClass:"department-owner-view-checkbox",attrs:{label:s.id},nativeOn:{click:function(n){n.stopPropagation()}}},[e("span")])],1)}),0)],1),e("div",{staticClass:"adaption",attrs:{slot:"footer"},slot:"footer"},[e("Button",{attrs:{type:"default",disabled:t.applyLoading},on:{click:function(s){return t.$emit("input",!1)}}},[t._v(t._s(t.$L("\u53D6\u6D88")))]),e("Button",{attrs:{type:"primary",loading:t.applyLoading},on:{click:t.apply}},[t._v(t._s(t.$L("\u786E\u5B9A")))])],1)])},l=[];const c={name:"DepartmentOwnerView",props:{value:Boolean},data(){return{draftIds:[],applyLoading:!1}},computed:{...i(["userInfo","cacheDepartmentOwnerIds"]),managedDepartments(){return(this.userInfo.managed_departments||[]).map(t=>({...t,id:parseInt(t.id)}))}},watch:{value:{immediate:!0,handler(t){t?this.draftIds=(this.cacheDepartmentOwnerIds||[]).slice():this.applyLoading=!1}}},methods:{toggleDraft(t){t=parseInt(t);const a=this.draftIds.indexOf(t);a>-1?this.draftIds.splice(a,1):this.draftIds.push(t)},reverseDraft(){const t=this.draftIds.map(a=>parseInt(a));this.draftIds=this.managedDepartments.map(a=>a.id).filter(a=>!t.includes(a))},async apply(){if(!this.applyLoading){this.applyLoading=!0;try{await this.$store.dispatch("setDepartmentOwnerIds",this.draftIds),this.$emit("input",!1)}catch(t){$A.modalError((t==null?void 0:t.msg)||this.$L("\u5207\u6362\u5931\u8D25"))}finally{this.applyLoading=!1}}}}},r={};var p=o(c,d,l,!1,f,"624ab3e4",null,null);function f(t){for(let a in r)this[a]=r[a]}var u=function(){return p.exports}();export{u as D};
|
||||
import{m as i}from"./vuex.cc7cb26e.js";import{n as o}from"./app.918d02da.js";var d=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("Modal",{attrs:{value:t.value,title:t.$L("\u8D1F\u8D23\u4EBA\u89C6\u89D2"),"mask-closable":!1,width:"520"},on:{input:function(s){return t.$emit("input",s)}}},[e("div",{staticClass:"department-owner-view-modal"},[e("Alert",{attrs:{type:"info","show-icon":""}},[t._v(" "+t._s(t.$L("\u53EF\u67E5\u770B\u6240\u9009\u90E8\u95E8\u53CA\u6240\u6709\u4E0B\u7EA7\u90E8\u95E8\u6210\u5458\u53C2\u4E0E\u7684\u9879\u76EE\u548C\u4EFB\u52A1\uFF0C\u4EC5\u652F\u6301\u53EA\u8BFB\u67E5\u770B\u3002"))+" ")]),t.managedDepartments.length>1?e("div",{staticClass:"department-owner-view-actions"},[e("a",{attrs:{href:"javascript:void(0)"},on:{click:function(s){t.draftIds=[]}}},[t._v(t._s(t.$L("\u6E05\u7A7A")))]),e("a",{attrs:{href:"javascript:void(0)"},on:{click:function(s){t.draftIds=t.managedDepartments.map(function(n){return n.id})}}},[t._v(t._s(t.$L("\u5168\u9009")))]),e("a",{attrs:{href:"javascript:void(0)"},on:{click:t.reverseDraft}},[t._v(t._s(t.$L("\u53CD\u9009")))])]):t._e(),e("CheckboxGroup",{staticClass:"department-owner-view-list",model:{value:t.draftIds,callback:function(s){t.draftIds=s},expression:"draftIds"}},t._l(t.managedDepartments,function(s){return e("div",{key:s.id,class:["department-owner-view-item",t.draftIds.includes(s.id)?"active":""],on:{click:function(n){return t.toggleDraft(s.id)}}},[e("div",{staticClass:"department-owner-view-icon"},[e("i",{staticClass:"taskfont"},[t._v("\uE75C")])]),e("div",{staticClass:"department-owner-view-name"},[t._v(t._s(s.name))]),e("Checkbox",{staticClass:"department-owner-view-checkbox",attrs:{label:s.id},nativeOn:{click:function(n){n.stopPropagation()}}},[e("span")])],1)}),0)],1),e("div",{staticClass:"adaption",attrs:{slot:"footer"},slot:"footer"},[e("Button",{attrs:{type:"default",disabled:t.applyLoading},on:{click:function(s){return t.$emit("input",!1)}}},[t._v(t._s(t.$L("\u53D6\u6D88")))]),e("Button",{attrs:{type:"primary",loading:t.applyLoading},on:{click:t.apply}},[t._v(t._s(t.$L("\u786E\u5B9A")))])],1)])},l=[];const c={name:"DepartmentOwnerView",props:{value:Boolean},data(){return{draftIds:[],applyLoading:!1}},computed:{...i(["userInfo","cacheDepartmentOwnerIds"]),managedDepartments(){return(this.userInfo.managed_departments||[]).map(t=>({...t,id:parseInt(t.id)}))}},watch:{value:{immediate:!0,handler(t){t?this.draftIds=(this.cacheDepartmentOwnerIds||[]).slice():this.applyLoading=!1}}},methods:{toggleDraft(t){t=parseInt(t);const a=this.draftIds.indexOf(t);a>-1?this.draftIds.splice(a,1):this.draftIds.push(t)},reverseDraft(){const t=this.draftIds.map(a=>parseInt(a));this.draftIds=this.managedDepartments.map(a=>a.id).filter(a=>!t.includes(a))},async apply(){if(!this.applyLoading){this.applyLoading=!0;try{await this.$store.dispatch("setDepartmentOwnerIds",this.draftIds),this.$emit("input",!1)}catch(t){$A.modalError((t==null?void 0:t.msg)||this.$L("\u5207\u6362\u5931\u8D25"))}finally{this.applyLoading=!1}}}}},r={};var p=o(c,d,l,!1,f,"624ab3e4",null,null);function f(t){for(let a in r)this[a]=r[a]}var u=function(){return p.exports}();export{u as D};
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{m as s}from"./vuex.cc7cb26e.js";import{I as m}from"./IFrame.587d7378.js";import{n as p,l as o}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var l=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"drawio-content"},[i("IFrame",{ref:"frame",staticClass:"drawio-iframe",attrs:{src:t.url},on:{"on-message":t.onMessage}}),t.loadIng?i("div",{staticClass:"drawio-loading"},[i("Loading")],1):t._e()],1)},d=[];const u={name:"Drawio",components:{IFrame:m},props:{value:{type:Object,default:function(){return{}}},title:{type:String,default:""},readOnly:{type:Boolean,default:!1}},data(){return{loadIng:!0,url:null,bakData:""}},created(){let t=o;switch(o){case"zh-CHT":t="zh-tw";break}let e=this.readOnly?1:0,i=this.readOnly?0:1,n=this.themeName==="dark"?"dark":"kennedy",r=`?title=${this.title?encodeURIComponent(this.title):""}&chrome=${i}&lightbox=${e}&ui=${n}&lang=${t}&offline=1&pwa=0&embed=1&noLangIcon=1&noExitBtn=1&noSaveBtn=1&saveAndExit=0&spin=1&proto=json`;this.$Electron?this.url=$A.originUrl(`drawio/webapp/index.html${r}`):this.url=$A.mainUrl(`drawio/webapp/${r}`)},mounted(){window.addEventListener("message",this.handleMessage)},beforeDestroy(){window.removeEventListener("message",this.handleMessage)},watch:{value:{handler(t){this.bakData!=$A.jsonStringify(t)&&(this.bakData=$A.jsonStringify(t),this.updateContent())},deep:!0}},computed:{...s(["themeName"])},methods:{formatZoom(t){return t+"%"},updateContent(){this.$refs.frame.postMessage(JSON.stringify({action:"load",autosave:1,xml:this.value.xml}))},onMessage(t){switch(t.event){case"init":this.loadIng=!1,this.updateContent();break;case"load":typeof this.value.xml=="undefined"&&this.$refs.frame.postMessage(JSON.stringify({action:"template"}));break;case"autosave":const e={xml:t.xml};this.bakData=$A.jsonStringify(e),this.$emit("input",e);break;case"save":this.$emit("saveData");break}}}},a={};var c=p(u,l,d,!1,h,"39021859",null,null);function h(t){for(let e in a)this[e]=a[e]}var pt=function(){return c.exports}();export{pt as default};
|
||||
import{m as s}from"./vuex.cc7cb26e.js";import{I as m}from"./IFrame.59abffe9.js";import{n as p,l as o}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var l=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"drawio-content"},[i("IFrame",{ref:"frame",staticClass:"drawio-iframe",attrs:{src:t.url},on:{"on-message":t.onMessage}}),t.loadIng?i("div",{staticClass:"drawio-loading"},[i("Loading")],1):t._e()],1)},d=[];const u={name:"Drawio",components:{IFrame:m},props:{value:{type:Object,default:function(){return{}}},title:{type:String,default:""},readOnly:{type:Boolean,default:!1}},data(){return{loadIng:!0,url:null,bakData:""}},created(){let t=o;switch(o){case"zh-CHT":t="zh-tw";break}let e=this.readOnly?1:0,i=this.readOnly?0:1,n=this.themeName==="dark"?"dark":"kennedy",r=`?title=${this.title?encodeURIComponent(this.title):""}&chrome=${i}&lightbox=${e}&ui=${n}&lang=${t}&offline=1&pwa=0&embed=1&noLangIcon=1&noExitBtn=1&noSaveBtn=1&saveAndExit=0&spin=1&proto=json`;this.$Electron?this.url=$A.originUrl(`drawio/webapp/index.html${r}`):this.url=$A.mainUrl(`drawio/webapp/${r}`)},mounted(){window.addEventListener("message",this.handleMessage)},beforeDestroy(){window.removeEventListener("message",this.handleMessage)},watch:{value:{handler(t){this.bakData!=$A.jsonStringify(t)&&(this.bakData=$A.jsonStringify(t),this.updateContent())},deep:!0}},computed:{...s(["themeName"])},methods:{formatZoom(t){return t+"%"},updateContent(){this.$refs.frame.postMessage(JSON.stringify({action:"load",autosave:1,xml:this.value.xml}))},onMessage(t){switch(t.event){case"init":this.loadIng=!1,this.updateContent();break;case"load":typeof this.value.xml=="undefined"&&this.$refs.frame.postMessage(JSON.stringify({action:"template"}));break;case"autosave":const e={xml:t.xml};this.bakData=$A.jsonStringify(e),this.$emit("input",e);break;case"save":this.$emit("saveData");break}}}},a={};var c=p(u,l,d,!1,h,"39021859",null,null);function h(t){for(let e in a)this[e]=a[e]}var pt=function(){return c.exports}();export{pt as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n}from"./app.27305e7a.js";var i=function(){var e=this,s=e.$createElement,r=e._self._c||s;return r("iframe",{directives:[{name:"show",rawName:"v-show",value:e.src,expression:"src"}],ref:"iframe",attrs:{src:e.src}})},a=[];const o={name:"IFrame",props:{src:{type:String,default:""}},mounted(){this.$refs.iframe.addEventListener("load",this.handleLoad),window.addEventListener("message",this.handleMessage)},beforeDestroy(){this.$refs.iframe.removeEventListener("load",this.handleLoad),window.removeEventListener("message",this.handleMessage)},methods:{handleLoad(){this.$emit("on-load")},handleMessage({data:e,source:s}){var r;s===((r=this.$refs.iframe)==null?void 0:r.contentWindow)&&(e=$A.jsonParse(e),e.source==="fileView"&&e.action==="picture"&&this.$store.dispatch("previewImage",{index:e.params.index,list:e.params.array}),this.$emit("on-message",e))},postMessage(e,s="*"){this.$refs.iframe&&this.$refs.iframe.contentWindow.postMessage(e,s)}}},t={};var m=n(o,i,a,!1,c,null,null,null);function c(e){for(let s in t)this[s]=t[s]}var l=function(){return m.exports}();export{l as I};
|
||||
import{n}from"./app.918d02da.js";var i=function(){var e=this,s=e.$createElement,r=e._self._c||s;return r("iframe",{directives:[{name:"show",rawName:"v-show",value:e.src,expression:"src"}],ref:"iframe",attrs:{src:e.src}})},a=[];const o={name:"IFrame",props:{src:{type:String,default:""}},mounted(){this.$refs.iframe.addEventListener("load",this.handleLoad),window.addEventListener("message",this.handleMessage)},beforeDestroy(){this.$refs.iframe.removeEventListener("load",this.handleLoad),window.removeEventListener("message",this.handleMessage)},methods:{handleLoad(){this.$emit("on-load")},handleMessage({data:e,source:s}){var r;s===((r=this.$refs.iframe)==null?void 0:r.contentWindow)&&(e=$A.jsonParse(e),e.source==="fileView"&&e.action==="picture"&&this.$store.dispatch("previewImage",{index:e.params.index,list:e.params.array}),this.$emit("on-message",e))},postMessage(e,s="*"){this.$refs.iframe&&this.$refs.iframe.contentWindow.postMessage(e,s)}}},t={};var m=n(o,i,a,!1,c,null,null,null);function c(e){for(let s in t)this[s]=t[s]}var l=function(){return m.exports}();export{l as I};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n as r}from"./app.27305e7a.js";var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return t.windowTouch?e("div",[e("Button",{attrs:{loading:t.loading,type:"primary",icon:"ios-search"},on:{click:t.onSearch}},[t._v(t._s(t.$L("\u641C\u7D22")))]),t.filtering?e("Button",{attrs:{type:"text"},on:{click:t.onCancelFilter}},[t._v(t._s(t.$L("\u53D6\u6D88\u7B5B\u9009")))]):e("Button",{attrs:{loading:t.loading,type:"text",icon:"md-refresh"},on:{click:t.onRefresh}},[t._v(t._s(t.$L("\u5237\u65B0")))])],1):e("Tooltip",{attrs:{theme:"light",placement:t.placement,"transfer-class-name":"search-button-clear",transfer:""}},[e("Button",{attrs:{loading:t.loading,type:"primary",icon:"ios-search"},on:{click:t.onSearch}},[t._v(t._s(t.$L("\u641C\u7D22")))]),e("div",{attrs:{slot:"content"},slot:"content"},[t.filtering?e("Button",{attrs:{type:"text"},on:{click:t.onCancelFilter}},[t._v(t._s(t.$L("\u53D6\u6D88\u7B5B\u9009")))]):e("Button",{attrs:{loading:t.loading,type:"text"},on:{click:t.onRefresh}},[t._v(t._s(t.$L("\u5237\u65B0")))])],1)],1)},i=[];const l={name:"SearchButton",props:{loading:{type:Boolean,default:!1},filtering:{type:Boolean,default:!1},placement:{type:String,default:"bottom"}},methods:{onSearch(){this.$emit("search")},onRefresh(){this.$emit("refresh")},onCancelFilter(){this.$emit("cancelFilter")}}},o={};var s=r(l,a,i,!1,c,null,null,null);function c(t){for(let n in o)this[n]=o[n]}var h=function(){return s.exports}();export{h as S};
|
||||
import{n as r}from"./app.918d02da.js";var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return t.windowTouch?e("div",[e("Button",{attrs:{loading:t.loading,type:"primary",icon:"ios-search"},on:{click:t.onSearch}},[t._v(t._s(t.$L("\u641C\u7D22")))]),t.filtering?e("Button",{attrs:{type:"text"},on:{click:t.onCancelFilter}},[t._v(t._s(t.$L("\u53D6\u6D88\u7B5B\u9009")))]):e("Button",{attrs:{loading:t.loading,type:"text",icon:"md-refresh"},on:{click:t.onRefresh}},[t._v(t._s(t.$L("\u5237\u65B0")))])],1):e("Tooltip",{attrs:{theme:"light",placement:t.placement,"transfer-class-name":"search-button-clear",transfer:""}},[e("Button",{attrs:{loading:t.loading,type:"primary",icon:"ios-search"},on:{click:t.onSearch}},[t._v(t._s(t.$L("\u641C\u7D22")))]),e("div",{attrs:{slot:"content"},slot:"content"},[t.filtering?e("Button",{attrs:{type:"text"},on:{click:t.onCancelFilter}},[t._v(t._s(t.$L("\u53D6\u6D88\u7B5B\u9009")))]):e("Button",{attrs:{loading:t.loading,type:"text"},on:{click:t.onRefresh}},[t._v(t._s(t.$L("\u5237\u65B0")))])],1)],1)},i=[];const l={name:"SearchButton",props:{loading:{type:Boolean,default:!1},filtering:{type:Boolean,default:!1},placement:{type:String,default:"bottom"}},methods:{onSearch(){this.$emit("search")},onRefresh(){this.$emit("refresh")},onCancelFilter(){this.$emit("cancelFilter")}}},o={};var s=r(l,a,i,!1,c,null,null,null);function c(t){for(let n in o)this[n]=o[n]}var h=function(){return s.exports}();export{h as S};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{m}from"./vuex.cc7cb26e.js";import{M as e}from"./index.5a5444b0.js";import{n as a}from"./app.27305e7a.js";import"./vue.adba9046.js";import"./@babel.9410f858.js";import"./view-design-hi.f1128b4d.js";import"./@micro-zoe.39406924.js";import"./DialogWrapper.f9684b46.js";import"./index.925989a2.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.2b547ee9.js";import"./webhook.378987f3.js";import"./jquery.9a8e34a6.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./html-to-md.f297036e.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var n=function(){var t=this,o=t.$createElement,r=t._self._c||o;return r("MicroApps",{ref:"app",attrs:{"window-type":"popout"}})},s=[];const u={components:{MicroApps:e},computed:{...m(["userIsAdmin"])},async mounted(){const{name:t}=this.$route.params;if(!t){$A.modalError("\u5E94\u7528\u4E0D\u5B58\u5728");return}if(t==="iframe-test"){if(!this.userIsAdmin){$A.modalError("\u4EC5\u7BA1\u7406\u5458\u53EF\u4F7F\u7528\u6B64\u529F\u80FD");return}let{url:r}=this.$route.query;if(!r){if(r=await this.promptIframeUrl(),!r)return;this.$router.replace({path:this.$route.path,query:{...this.$route.query,url:r}}).catch(()=>{})}await this.$refs.app.onOpen({id:"iframe-test",name:"iframe-test",url:r,type:"iframe",transparent:!0,keep_alive:!1});return}const o=(await $A.IDBArray("cacheMicroApps")).reverse().find(r=>r.name===t);if(!o){$A.modalError("\u5E94\u7528\u4E0D\u5B58\u5728");return}await this.$refs.app.onOpen(o)},methods:{promptIframeUrl(){return new Promise((t,o)=>{$A.modalInput({title:this.$L("\u8BF7\u8F93\u5165 URL"),placeholder:"https://example.com",onOk:r=>{const i=(r||"").trim();if(!i)return this.$L("URL\u4E0D\u80FD\u4E3A\u7A7A");t(i)},onCancel:()=>o()})}).catch(()=>null)}}},p={};var c=a(u,n,s,!1,l,null,null,null);function l(t){for(let o in p)this[o]=p[o]}var lr=function(){return c.exports}();export{lr as default};
|
||||
import{m}from"./vuex.cc7cb26e.js";import{M as e}from"./index.df9b95aa.js";import{n as a}from"./app.918d02da.js";import"./vue.adba9046.js";import"./@babel.9410f858.js";import"./view-design-hi.f1128b4d.js";import"./@micro-zoe.39406924.js";import"./DialogWrapper.9119970a.js";import"./index.d93bb128.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.1cc3ab34.js";import"./webhook.378987f3.js";import"./jquery.b179464f.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./html-to-md.f297036e.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var n=function(){var t=this,o=t.$createElement,r=t._self._c||o;return r("MicroApps",{ref:"app",attrs:{"window-type":"popout"}})},s=[];const u={components:{MicroApps:e},computed:{...m(["userIsAdmin"])},async mounted(){const{name:t}=this.$route.params;if(!t){$A.modalError("\u5E94\u7528\u4E0D\u5B58\u5728");return}if(t==="iframe-test"){if(!this.userIsAdmin){$A.modalError("\u4EC5\u7BA1\u7406\u5458\u53EF\u4F7F\u7528\u6B64\u529F\u80FD");return}let{url:r}=this.$route.query;if(!r){if(r=await this.promptIframeUrl(),!r)return;this.$router.replace({path:this.$route.path,query:{...this.$route.query,url:r}}).catch(()=>{})}await this.$refs.app.onOpen({id:"iframe-test",name:"iframe-test",url:r,type:"iframe",transparent:!0,keep_alive:!1});return}const o=(await $A.IDBArray("cacheMicroApps")).reverse().find(r=>r.name===t);if(!o){$A.modalError("\u5E94\u7528\u4E0D\u5B58\u5728");return}await this.$refs.app.onOpen(o)},methods:{promptIframeUrl(){return new Promise((t,o)=>{$A.modalInput({title:this.$L("\u8BF7\u8F93\u5165 URL"),placeholder:"https://example.com",onOk:r=>{const i=(r||"").trim();if(!i)return this.$L("URL\u4E0D\u80FD\u4E3A\u7A7A");t(i)},onCancel:()=>o()})}).catch(()=>null)}}},p={};var c=a(u,n,s,!1,l,null,null,null);function l(t){for(let o in p)this[o]=p[o]}var lr=function(){return c.exports}();export{lr as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n as l}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var m=function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"setting-device"},[i("ul",[t.loadIng>0&&t.devices.length===0?i("li",{staticClass:"loading"},[i("Loading")],1):t._l(t.devices,function(e){return i("li",{key:e.id},[i("div",{staticClass:"icon"},[i("span",{class:t.getIcon(e.detail)})]),i("div",{staticClass:"info"},[i("div",{staticClass:"title"},[i("span",{staticClass:"name"},[t._v(t._s(t.getName(e.detail)))]),i("span",{staticClass:"device"},[t._v(t._s(t.getOs(e.detail)))])]),i("div",{staticClass:"time"},[i("EPopover",{attrs:{placement:"bottom-start",trigger:"click"}},[i("div",{staticClass:"setting-device-popover"},[i("p",[t._v(t._s(t.$L("\u767B\u5F55\u65F6\u95F4"))+": "+t._s(e.created_at))]),i("p",[t._v(t._s(t.$L("\u66F4\u65B0\u65F6\u95F4"))+": "+t._s(e.updated_at))]),i("p",[t._v(t._s(t.$L("\u8FC7\u671F\u65F6\u95F4"))+": "+t._s(e.expired_at))])]),i("span",{attrs:{slot:"reference"},slot:"reference"},[t._v(t._s(e.updated_at))])])],1)]),i("div",[e.is_current?i("span",{staticClass:"current"},[t._v(t._s(t.$L("\u5F53\u524D\u8BBE\u5907")))]):i("Button",{on:{click:function(o){return t.onLogout(e)}}},[t._v(t._s(t.$L("\u9000\u51FA\u767B\u5F55")))])],1)])})],2)])},p=[];const c={name:"SettingDevice",data(){return{loadIng:0,devices:[]}},mounted(){this.getDeviceList()},methods:{getDeviceList(){this.loadIng++,this.$store.dispatch("call",{url:"users/device/list"}).then(({data:t})=>{this.devices=t.list,typeof this.$parent.updateDeviceCount=="function"&&this.$parent.updateDeviceCount(this.devices.length)}).catch(({msg:t})=>{$A.modalError(t),this.devices=[]}).finally(()=>{this.loadIng--})},getIcon({app_type:t,app_name:r}){return/ios/i.test(t)?/ipad/i.test(r)?"tablet":/iphone/i.test(r)?"phone":"apple":/android/i.test(t)?/(tablet|phablet)/i.test(r)?"tablet":"android":/mac/i.test(t)?"macos":/win/i.test(t)?"window":"web"},getName({app_brand:t,app_model:r,device_name:i,app_type:e,app_name:o,browser:a}){const s=[];if(/web/i.test(e))s.push(a,this.$L("\u6D4F\u89C8\u5668"));else{if(i)return i;t?s.push(t,r):s.push(o||e,this.$L("\u5BA2\u6237\u7AEF"))}return s.join(" ")},getOs({app_os:t,os:r}){return t||r},onLogout(t){$A.modalConfirm({title:"\u9000\u51FA\u767B\u5F55",content:"\u662F\u5426\u5728\u8BE5\u8BBE\u5907\u4E0A\u9000\u51FA\u767B\u5F55\uFF1F",loading:!0,onOk:()=>new Promise((r,i)=>{this.$store.dispatch("call",{url:"users/device/logout",data:{id:t.id}}).then(({msg:e})=>{r(e),this.getDeviceList()}).catch(({msg:e})=>{i(e)})})})}}},n={};var u=l(c,m,p,!1,d,null,null,null);function d(t){for(let r in n)this[r]=n[r]}var nt=function(){return u.exports}();export{nt as default};
|
||||
import{n as l}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var m=function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"setting-device"},[i("ul",[t.loadIng>0&&t.devices.length===0?i("li",{staticClass:"loading"},[i("Loading")],1):t._l(t.devices,function(e){return i("li",{key:e.id},[i("div",{staticClass:"icon"},[i("span",{class:t.getIcon(e.detail)})]),i("div",{staticClass:"info"},[i("div",{staticClass:"title"},[i("span",{staticClass:"name"},[t._v(t._s(t.getName(e.detail)))]),i("span",{staticClass:"device"},[t._v(t._s(t.getOs(e.detail)))])]),i("div",{staticClass:"time"},[i("EPopover",{attrs:{placement:"bottom-start",trigger:"click"}},[i("div",{staticClass:"setting-device-popover"},[i("p",[t._v(t._s(t.$L("\u767B\u5F55\u65F6\u95F4"))+": "+t._s(e.created_at))]),i("p",[t._v(t._s(t.$L("\u66F4\u65B0\u65F6\u95F4"))+": "+t._s(e.updated_at))]),i("p",[t._v(t._s(t.$L("\u8FC7\u671F\u65F6\u95F4"))+": "+t._s(e.expired_at))])]),i("span",{attrs:{slot:"reference"},slot:"reference"},[t._v(t._s(e.updated_at))])])],1)]),i("div",[e.is_current?i("span",{staticClass:"current"},[t._v(t._s(t.$L("\u5F53\u524D\u8BBE\u5907")))]):i("Button",{on:{click:function(o){return t.onLogout(e)}}},[t._v(t._s(t.$L("\u9000\u51FA\u767B\u5F55")))])],1)])})],2)])},p=[];const c={name:"SettingDevice",data(){return{loadIng:0,devices:[]}},mounted(){this.getDeviceList()},methods:{getDeviceList(){this.loadIng++,this.$store.dispatch("call",{url:"users/device/list"}).then(({data:t})=>{this.devices=t.list,typeof this.$parent.updateDeviceCount=="function"&&this.$parent.updateDeviceCount(this.devices.length)}).catch(({msg:t})=>{$A.modalError(t),this.devices=[]}).finally(()=>{this.loadIng--})},getIcon({app_type:t,app_name:r}){return/ios/i.test(t)?/ipad/i.test(r)?"tablet":/iphone/i.test(r)?"phone":"apple":/android/i.test(t)?/(tablet|phablet)/i.test(r)?"tablet":"android":/mac/i.test(t)?"macos":/win/i.test(t)?"window":"web"},getName({app_brand:t,app_model:r,device_name:i,app_type:e,app_name:o,browser:a}){const s=[];if(/web/i.test(e))s.push(a,this.$L("\u6D4F\u89C8\u5668"));else{if(i)return i;t?s.push(t,r):s.push(o||e,this.$L("\u5BA2\u6237\u7AEF"))}return s.join(" ")},getOs({app_os:t,os:r}){return t||r},onLogout(t){$A.modalConfirm({title:"\u9000\u51FA\u767B\u5F55",content:"\u662F\u5426\u5728\u8BE5\u8BBE\u5907\u4E0A\u9000\u51FA\u767B\u5F55\uFF1F",loading:!0,onOk:()=>new Promise((r,i)=>{this.$store.dispatch("call",{url:"users/device/logout",data:{id:t.id}}).then(({msg:e})=>{r(e),this.getDeviceList()}).catch(({msg:e})=>{i(e)})})})}}},n={};var u=l(c,m,p,!1,d,null,null,null);function d(t){for(let r in n)this[r]=n[r]}var nt=function(){return u.exports}();export{nt as default};
|
||||
@ -1 +1 @@
|
||||
import{D as p}from"./DialogWrapper.f9684b46.js";import{m}from"./vuex.cc7cb26e.js";import{n as a}from"./app.27305e7a.js";import"./index.925989a2.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.2b547ee9.js";import"./webhook.378987f3.js";import"./jquery.9a8e34a6.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,r=t._self._c||o;return r("div",{staticClass:"electron-dialog"},[r("PageTitle",{attrs:{title:t.dialogData.name}}),t.dialogId>0?r("DialogWrapper",{attrs:{dialogId:t.dialogId}}):t._e()],1)},n=[];const s={components:{DialogWrapper:p},computed:{...m(["cacheDialogs"]),dialogId(){const{dialogId:t}=this.$route.params;return parseInt(/^\d+$/.test(t)?t:0)},dialogData(){return this.cacheDialogs.find(({id:t})=>t===this.dialogId)||{}}}},i={};var l=a(s,e,n,!1,d,"4f6d7c8a",null,null);function d(t){for(let o in i)this[o]=i[o]}var st=function(){return l.exports}();export{st as default};
|
||||
import{D as p}from"./DialogWrapper.9119970a.js";import{m}from"./vuex.cc7cb26e.js";import{n as a}from"./app.918d02da.js";import"./index.d93bb128.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.1cc3ab34.js";import"./webhook.378987f3.js";import"./jquery.b179464f.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,r=t._self._c||o;return r("div",{staticClass:"electron-dialog"},[r("PageTitle",{attrs:{title:t.dialogData.name}}),t.dialogId>0?r("DialogWrapper",{attrs:{dialogId:t.dialogId}}):t._e()],1)},n=[];const s={components:{DialogWrapper:p},computed:{...m(["cacheDialogs"]),dialogId(){const{dialogId:t}=this.$route.params;return parseInt(/^\d+$/.test(t)?t:0)},dialogData(){return this.cacheDialogs.find(({id:t})=>t===this.dialogId)||{}}}},i={};var l=a(s,e,n,!1,d,"4f6d7c8a",null,null);function d(t){for(let o in i)this[o]=i[o]}var st=function(){return l.exports}();export{st as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import n from"./FileContent.e616cb2a.js";import m from"./FilePreview.5e951915.js";import{n as l}from"./app.27305e7a.js";import"./openpgp_hi.15f91b1d.js";import"./IFrame.587d7378.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"single-file"},[i("PageTitle",{attrs:{title:t.pageName}}),t.loadIng>0?i("Loading"):t.fileInfo?[t.isPreview?i("FilePreview",{attrs:{code:t.code,file:t.fileInfo,historyId:t.historyId,headerShow:!t.$isEEUIApp}}):i("FileContent",{attrs:{file:t.fileInfo},model:{value:t.fileShow,callback:function(r){t.fileShow=r},expression:"fileShow"}})]:t._e()],2)},p=[];const a={components:{FilePreview:m,FileContent:n},data(){return{loadIng:0,code:null,fileShow:!0,fileInfo:null}},mounted(){},computed:{historyId(){return this.$route.query?$A.runNum(this.$route.query.history_id):0},isPreview(){return this.windowPortrait||this.code||this.historyId>0||this.fileInfo&&this.fileInfo.permission===0},pageName(){return this.$route.query&&this.$route.query.history_title?this.$route.query.history_title:this.fileInfo?`${this.fileInfo.name} [${this.fileInfo.created_at}]`:""}},watch:{$route:{handler(){this.getInfo()},immediate:!0}},methods:{getInfo(){let{codeOrFileId:t}=this.$route.params,e={id:t};if(/^\d+$/.test(t))this.code=null;else if(t)this.code=t;else return;setTimeout(i=>{this.loadIng++},600),this.$store.dispatch("call",{url:"file/one",data:e}).then(({data:i})=>{this.fileInfo=i}).catch(({msg:i})=>{$A.modalError({content:i,onOk:()=>{window.close()}})}).finally(i=>{this.loadIng--})}}},o={};var f=l(a,s,p,!1,u,"662d0b64",null,null);function u(t){for(let e in o)this[e]=o[e]}var st=function(){return f.exports}();export{st as default};
|
||||
import n from"./FileContent.dbc18a13.js";import m from"./FilePreview.a3eaba28.js";import{n as l}from"./app.918d02da.js";import"./openpgp_hi.15f91b1d.js";import"./IFrame.59abffe9.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"single-file"},[i("PageTitle",{attrs:{title:t.pageName}}),t.loadIng>0?i("Loading"):t.fileInfo?[t.isPreview?i("FilePreview",{attrs:{code:t.code,file:t.fileInfo,historyId:t.historyId,headerShow:!t.$isEEUIApp}}):i("FileContent",{attrs:{file:t.fileInfo},model:{value:t.fileShow,callback:function(r){t.fileShow=r},expression:"fileShow"}})]:t._e()],2)},p=[];const a={components:{FilePreview:m,FileContent:n},data(){return{loadIng:0,code:null,fileShow:!0,fileInfo:null}},mounted(){},computed:{historyId(){return this.$route.query?$A.runNum(this.$route.query.history_id):0},isPreview(){return this.windowPortrait||this.code||this.historyId>0||this.fileInfo&&this.fileInfo.permission===0},pageName(){return this.$route.query&&this.$route.query.history_title?this.$route.query.history_title:this.fileInfo?`${this.fileInfo.name} [${this.fileInfo.created_at}]`:""}},watch:{$route:{handler(){this.getInfo()},immediate:!0}},methods:{getInfo(){let{codeOrFileId:t}=this.$route.params,e={id:t};if(/^\d+$/.test(t))this.code=null;else if(t)this.code=t;else return;setTimeout(i=>{this.loadIng++},600),this.$store.dispatch("call",{url:"file/one",data:e}).then(({data:i})=>{this.fileInfo=i}).catch(({msg:i})=>{$A.modalError({content:i,onOk:()=>{window.close()}})}).finally(i=>{this.loadIng--})}}},o={};var f=l(a,s,p,!1,u,"662d0b64",null,null);function u(t){for(let e in o)this[e]=o[e]}var st=function(){return f.exports}();export{st as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n as m}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,i=t._self._c||o;return i("div")},n=[];const p={data(){return{}},mounted(){if(/^https?:/i.test(window.location.protocol)){let t=null;if(this.$router.mode==="hash"?$A.stringLength(window.location.pathname)>2&&(t=`${window.location.origin}/#${window.location.pathname}${window.location.search}`):this.$router.mode==="history"&&$A.strExists(window.location.href,"/#/")&&(t=window.location.href.replace("/#/","/")),t)throw this.$store.dispatch("userUrl",t).then(o=>{window.location.href=o}),SyntaxError()}},activated(){this.start()},methods:{start(){this.userId>0?this.goForward({name:"manage-dashboard"},!0):this.goForward({name:"login"},!0)}}},r={};var a=m(p,e,n,!1,s,null,null,null);function s(t){for(let o in r)this[o]=r[o]}var rt=function(){return a.exports}();export{rt as default};
|
||||
import{n as m}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,i=t._self._c||o;return i("div")},n=[];const p={data(){return{}},mounted(){if(/^https?:/i.test(window.location.protocol)){let t=null;if(this.$router.mode==="hash"?$A.stringLength(window.location.pathname)>2&&(t=`${window.location.origin}/#${window.location.pathname}${window.location.search}`):this.$router.mode==="history"&&$A.strExists(window.location.href,"/#/")&&(t=window.location.href.replace("/#/","/")),t)throw this.$store.dispatch("userUrl",t).then(o=>{window.location.href=o}),SyntaxError()}},activated(){this.start()},methods:{start(){this.userId>0?this.goForward({name:"manage-dashboard"},!0):this.goForward({name:"login"},!0)}}},r={};var a=m(p,e,n,!1,s,null,null,null);function s(t){for(let o in r)this[o]=r[o]}var rt=function(){return a.exports}();export{rt as default};
|
||||
@ -1 +1 @@
|
||||
import{_ as m}from"./openpgp_hi.15f91b1d.js";import{e as n}from"./index.40a8e116.js";import{n as p}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var a=function(){var t=this,o=t.$createElement,i=t._self._c||o;return t.ready?i("VEditor",{attrs:{leftToolbar:t.leftToolbar,rightToolbar:t.rightToolbar,tocNavPositionRight:t.tocNavPositionRight,includeLevel:t.includeLevel},model:{value:t.content,callback:function(e){t.content=e},expression:"content"}}):i("Loading")},s=[];const l={name:"VMEditor",mixins:[n],components:{VEditor:()=>m(()=>import("./editor.8000618c.js"),["js/build/editor.8000618c.js","js/build/editor.90492550.css","js/build/@kangc.b5fe0a56.js","js/build/@kangc.d8464d83.css","js/build/@babel.9410f858.js","js/build/vue.adba9046.js","js/build/copy-to-clipboard.a53c061d.js","js/build/toggle-selection.d2487283.js","js/build/prismjs.94ec9288.js","js/build/app.27305e7a.js","js/build/app.1bed8789.css","js/build/jquery.9a8e34a6.js","js/build/dayjs.22b500b5.js","js/build/localforage.ff736638.js","js/build/markdown-it.0450edb4.js","js/build/mdurl.ce6c1dd8.js","js/build/uc.micro.8d343c98.js","js/build/entities.48a44fec.js","js/build/linkify-it.c5e8196e.js","js/build/punycode.js.4b3f125a.js","js/build/highlight.js.cbbfb885.js","js/build/markdown-it-link-attributes.e1d5d151.js","js/build/@traptitech.acea8861.js","js/build/vuex.cc7cb26e.js","js/build/openpgp_hi.15f91b1d.js","js/build/axios.37c7f908.js","js/build/mitt.1ea0a2a3.js","js/build/quill-hi.ca2ea0cc.js","js/build/parchment.d5c5924e.js","js/build/quill-delta.385a10bf.js","js/build/fast-diff.f17881f3.js","js/build/lodash.clonedeep.3cc09a31.js","js/build/lodash.isequal.dbdc2157.js","js/build/eventemitter3.78b735ad.js","js/build/lodash-es.76e3a28b.js","js/build/quill-mention-hi.4eeb5a2d.js","js/build/view-design-hi.f1128b4d.js","js/build/html-to-md.f297036e.js","js/build/lodash.8fcd6fd4.js","js/build/vue-router.2d566cd7.js","js/build/vue-clipboard2.fd43a5bc.js","js/build/clipboard.37b37361.js","js/build/vuedraggable.f464b992.js","js/build/sortablejs.3488b922.js","js/build/vue-resize-observer.5af23a43.js","js/build/element-sea.f8a64907.js","js/build/deepmerge.cecf392e.js","js/build/resize-observer-polyfill.5d591c5f.js","js/build/throttle-debounce.7c3948b2.js","js/build/babel-helper-vue-jsx-merge-props.5ed215c3.js","js/build/normalize-wheel.2a034b9f.js","js/build/async-validator.dca2b951.js","js/build/babel-runtime.4773988a.js","js/build/core-js.314b4a1d.js","js/build/codemirror.9d10b9e4.js","js/build/codemirror.9ace6687.css","js/build/index.40a8e116.js","js/build/ImgUpload.2b547ee9.js"])},data(){return{ready:!1,content:""}},async mounted(){await $A.loadScriptS(["js/katex/katex.min.js","js/katex/katex.min.css","js/mermaid.min.js"]),this.ready=!0},watch:{value:{handler(t){t==null&&(t=""),this.content=t},immediate:!0},content(t){this.$emit("input",t)}}},r={};var c=p(l,a,s,!1,_,null,null,null);function _(t){for(let o in r)this[o]=r[o]}var nt=function(){return c.exports}();export{nt as default};
|
||||
import{_ as m}from"./openpgp_hi.15f91b1d.js";import{e as n}from"./index.40a8e116.js";import{n as p}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var a=function(){var t=this,o=t.$createElement,i=t._self._c||o;return t.ready?i("VEditor",{attrs:{leftToolbar:t.leftToolbar,rightToolbar:t.rightToolbar,tocNavPositionRight:t.tocNavPositionRight,includeLevel:t.includeLevel},model:{value:t.content,callback:function(e){t.content=e},expression:"content"}}):i("Loading")},s=[];const l={name:"VMEditor",mixins:[n],components:{VEditor:()=>m(()=>import("./editor.86694f0e.js"),["js/build/editor.86694f0e.js","js/build/editor.90492550.css","js/build/@kangc.b5fe0a56.js","js/build/@kangc.d8464d83.css","js/build/@babel.9410f858.js","js/build/vue.adba9046.js","js/build/copy-to-clipboard.a53c061d.js","js/build/toggle-selection.d2487283.js","js/build/prismjs.94ec9288.js","js/build/app.918d02da.js","js/build/app.4f9fe422.css","js/build/jquery.b179464f.js","js/build/dayjs.7f198189.js","js/build/localforage.578a5228.js","js/build/markdown-it.0450edb4.js","js/build/mdurl.ce6c1dd8.js","js/build/uc.micro.8d343c98.js","js/build/entities.48a44fec.js","js/build/linkify-it.c5e8196e.js","js/build/punycode.js.4b3f125a.js","js/build/highlight.js.cbbfb885.js","js/build/markdown-it-link-attributes.e1d5d151.js","js/build/@traptitech.acea8861.js","js/build/vuex.cc7cb26e.js","js/build/openpgp_hi.15f91b1d.js","js/build/axios.37c7f908.js","js/build/mitt.1ea0a2a3.js","js/build/quill-hi.ca2ea0cc.js","js/build/parchment.d5c5924e.js","js/build/quill-delta.385a10bf.js","js/build/fast-diff.f17881f3.js","js/build/lodash.clonedeep.3cc09a31.js","js/build/lodash.isequal.dbdc2157.js","js/build/eventemitter3.78b735ad.js","js/build/lodash-es.76e3a28b.js","js/build/quill-mention-hi.4eeb5a2d.js","js/build/view-design-hi.f1128b4d.js","js/build/html-to-md.f297036e.js","js/build/lodash.8fcd6fd4.js","js/build/vue-router.2d566cd7.js","js/build/vue-clipboard2.fd43a5bc.js","js/build/clipboard.37b37361.js","js/build/vuedraggable.f464b992.js","js/build/sortablejs.3488b922.js","js/build/vue-resize-observer.5af23a43.js","js/build/element-sea.f8a64907.js","js/build/deepmerge.cecf392e.js","js/build/resize-observer-polyfill.5d591c5f.js","js/build/throttle-debounce.7c3948b2.js","js/build/babel-helper-vue-jsx-merge-props.5ed215c3.js","js/build/normalize-wheel.2a034b9f.js","js/build/async-validator.dca2b951.js","js/build/babel-runtime.4773988a.js","js/build/core-js.314b4a1d.js","js/build/codemirror.9d10b9e4.js","js/build/codemirror.9ace6687.css","js/build/index.40a8e116.js","js/build/ImgUpload.1cc3ab34.js"])},data(){return{ready:!1,content:""}},async mounted(){await $A.loadScriptS(["js/katex/katex.min.js","js/katex/katex.min.css","js/mermaid.min.js"]),this.ready=!0},watch:{value:{handler(t){t==null&&(t=""),this.content=t},immediate:!0},content(t){this.$emit("input",t)}}},r={};var c=p(l,a,s,!1,_,null,null,null);function _(t){for(let o in r)this[o]=r[o]}var nt=function(){return c.exports}();export{nt as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{h as e,l as n,r as s,n as p}from"./app.27305e7a.js";import{m as l}from"./vuex.cc7cb26e.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var u=function(){var t=this,a=t.$createElement,r=t._self._c||a;return r("div",{staticClass:"setting-item submit"},[r("Form",t._b({ref:"formData",attrs:{model:t.formData,rules:t.ruleData},nativeOn:{submit:function(o){o.preventDefault()}}},"Form",t.formOptions,!1),[r("FormItem",{attrs:{label:t.$L("\u9009\u62E9\u8BED\u8A00"),prop:"language"}},[r("Select",{attrs:{placeholder:t.$L("\u9009\u9879\u8BED\u8A00")},model:{value:t.formData.language,callback:function(o){t.$set(t.formData,"language",o)},expression:"formData.language"}},t._l(t.languageList,function(o,i){return r("Option",{key:i,attrs:{value:i}},[t._v(t._s(o))])}),1)],1)],1),r("div",{staticClass:"setting-footer"},[r("Button",{attrs:{loading:t.loadIng>0,type:"primary"},on:{click:t.submitForm}},[t._v(t._s(t.$L("\u63D0\u4EA4")))]),r("Button",{staticStyle:{"margin-left":"8px"},attrs:{loading:t.loadIng>0},on:{click:t.resetForm}},[t._v(t._s(t.$L("\u91CD\u7F6E")))])],1)],1)},f=[];const g={data(){return{loadIng:0,languageList:e,formData:{language:""},ruleData:{}}},mounted(){this.initData()},computed:{...l(["formOptions"])},methods:{initData(){this.$set(this.formData,"language",n),this.formData_bak=$A.cloneJSON(this.formData)},submitForm(){this.$refs.formData.validate(t=>{t&&s(this.formData.language)})},resetForm(){this.formData=$A.cloneJSON(this.formData_bak)}}},m={};var c=p(g,u,f,!1,_,null,null,null);function _(t){for(let a in m)this[a]=m[a]}var st=function(){return c.exports}();export{st as default};
|
||||
import{h as e,l as n,r as s,n as p}from"./app.918d02da.js";import{m as l}from"./vuex.cc7cb26e.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var u=function(){var t=this,a=t.$createElement,r=t._self._c||a;return r("div",{staticClass:"setting-item submit"},[r("Form",t._b({ref:"formData",attrs:{model:t.formData,rules:t.ruleData},nativeOn:{submit:function(o){o.preventDefault()}}},"Form",t.formOptions,!1),[r("FormItem",{attrs:{label:t.$L("\u9009\u62E9\u8BED\u8A00"),prop:"language"}},[r("Select",{attrs:{placeholder:t.$L("\u9009\u9879\u8BED\u8A00")},model:{value:t.formData.language,callback:function(o){t.$set(t.formData,"language",o)},expression:"formData.language"}},t._l(t.languageList,function(o,i){return r("Option",{key:i,attrs:{value:i}},[t._v(t._s(o))])}),1)],1)],1),r("div",{staticClass:"setting-footer"},[r("Button",{attrs:{loading:t.loadIng>0,type:"primary"},on:{click:t.submitForm}},[t._v(t._s(t.$L("\u63D0\u4EA4")))]),r("Button",{staticStyle:{"margin-left":"8px"},attrs:{loading:t.loadIng>0},on:{click:t.resetForm}},[t._v(t._s(t.$L("\u91CD\u7F6E")))])],1)],1)},f=[];const g={data(){return{loadIng:0,languageList:e,formData:{language:""},ruleData:{}}},mounted(){this.initData()},computed:{...l(["formOptions"])},methods:{initData(){this.$set(this.formData,"language",n),this.formData_bak=$A.cloneJSON(this.formData)},submitForm(){this.$refs.formData.validate(t=>{t&&s(this.formData.language)})},resetForm(){this.formData=$A.cloneJSON(this.formData_bak)}}},m={};var c=p(g,u,f,!1,_,null,null,null);function _(t){for(let a in m)this[a]=m[a]}var st=function(){return c.exports}();export{st as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n as a}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var i=this,t=i.$createElement,r=i._self._c||t;return r("div")},u=[];const c={mounted(){const{meetingId:i,sharekey:t}=this.$route.params,{nickname:r,avatar:m,audio:p,video:n,type:o}=this.$route.query;this.$store.dispatch("showMeetingWindow",{type:["direct","join"].includes(o)?o:"join",meetingid:i,meetingSharekey:t,meetingNickname:r,meetingAvatar:m,meetingAudio:p,meetingVideo:n,meetingdisabled:!0})},render(){return null}},e={};var d=a(c,s,u,!1,l,null,null,null);function l(i){for(let t in e)this[t]=e[t]}var pt=function(){return d.exports}();export{pt as default};
|
||||
import{n as a}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var i=this,t=i.$createElement,r=i._self._c||t;return r("div")},u=[];const c={mounted(){const{meetingId:i,sharekey:t}=this.$route.params,{nickname:r,avatar:m,audio:p,video:n,type:o}=this.$route.query;this.$store.dispatch("showMeetingWindow",{type:["direct","join"].includes(o)?o:"join",meetingid:i,meetingSharekey:t,meetingNickname:r,meetingAvatar:m,meetingAudio:p,meetingVideo:n,meetingdisabled:!0})},render(){return null}},e={};var d=a(c,s,u,!1,l,null,null,null);function l(i){for(let t in e)this[t]=e[t]}var pt=function(){return d.exports}();export{pt as default};
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{m as i}from"./vuex.cc7cb26e.js";import{n as m}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var a=function(){var t=this,s=t.$createElement,r=t._self._c||s;return r("div",{staticClass:"setting-item submit"},[r("Form",t._b({ref:"formDatum",attrs:{model:t.formDatum,rules:t.ruleDatum},nativeOn:{submit:function(e){e.preventDefault()}}},"Form",t.formOptions,!1),[t.userInfo.changepass?r("Alert",{staticStyle:{"margin-bottom":"32px"},attrs:{type:"warning",showIcon:""}},[t._v(t._s(t.$L("\u8BF7\u5148\u4FEE\u6539\u767B\u5F55\u5BC6\u7801\uFF01")))]):t._e(),r("FormItem",{attrs:{label:t.$L("\u65E7\u5BC6\u7801"),prop:"oldpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.oldpass,callback:function(e){t.$set(t.formDatum,"oldpass",e)},expression:"formDatum.oldpass"}})],1),r("FormItem",{attrs:{label:t.$L("\u65B0\u5BC6\u7801"),prop:"newpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.newpass,callback:function(e){t.$set(t.formDatum,"newpass",e)},expression:"formDatum.newpass"}})],1),r("FormItem",{attrs:{label:t.$L("\u786E\u8BA4\u65B0\u5BC6\u7801"),prop:"checkpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.checkpass,callback:function(e){t.$set(t.formDatum,"checkpass",e)},expression:"formDatum.checkpass"}})],1)],1),r("div",{staticClass:"setting-footer"},[r("Button",{attrs:{loading:t.loadIng>0,type:"primary"},on:{click:t.submitForm}},[t._v(t._s(t.$L("\u63D0\u4EA4")))]),r("Button",{staticStyle:{"margin-left":"8px"},attrs:{loading:t.loadIng>0},on:{click:t.resetForm}},[t._v(t._s(t.$L("\u91CD\u7F6E")))])],1)],1)},p=[];const n={data(){return{loadIng:0,formDatum:{oldpass:"",newpass:"",checkpass:""},ruleDatum:{oldpass:[{required:!0,message:this.$L("\u8BF7\u8F93\u5165\u65E7\u5BC6\u7801\uFF01"),trigger:"change"},{type:"string",min:6,message:this.$L("\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D\uFF01"),trigger:"change"}],newpass:[{validator:(t,s,r)=>{s===""?r(new Error(this.$L("\u8BF7\u8F93\u5165\u65B0\u5BC6\u7801\uFF01"))):(this.formDatum.checkpass!==""&&this.$refs.formDatum.validateField("checkpass"),r())},required:!0,trigger:"change"},{type:"string",min:6,message:this.$L("\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D\uFF01"),trigger:"change"}],checkpass:[{validator:(t,s,r)=>{s===""?r(new Error(this.$L("\u8BF7\u91CD\u65B0\u8F93\u5165\u65B0\u5BC6\u7801\uFF01"))):s!==this.formDatum.newpass?r(new Error(this.$L("\u4E24\u6B21\u5BC6\u7801\u8F93\u5165\u4E0D\u4E00\u81F4\uFF01"))):r()},required:!0,trigger:"change"}]}}},computed:{...i(["userInfo","formOptions"])},methods:{submitForm(){this.$refs.formDatum.validate(t=>{t&&(this.loadIng++,this.$store.dispatch("call",{url:"users/editpass",data:this.formDatum}).then(({data:s})=>{$A.messageSuccess("\u4FEE\u6539\u6210\u529F"),this.$store.dispatch("saveUserInfo",s),this.$refs.formDatum.resetFields()}).catch(({msg:s})=>{$A.modalError(s)}).finally(s=>{this.loadIng--}))})},resetForm(){this.$refs.formDatum.resetFields()}}},o={};var l=m(n,a,p,!1,u,null,null,null);function u(t){for(let s in o)this[s]=o[s]}var ot=function(){return l.exports}();export{ot as default};
|
||||
import{m as i}from"./vuex.cc7cb26e.js";import{n as m}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var a=function(){var t=this,s=t.$createElement,r=t._self._c||s;return r("div",{staticClass:"setting-item submit"},[r("Form",t._b({ref:"formDatum",attrs:{model:t.formDatum,rules:t.ruleDatum},nativeOn:{submit:function(e){e.preventDefault()}}},"Form",t.formOptions,!1),[t.userInfo.changepass?r("Alert",{staticStyle:{"margin-bottom":"32px"},attrs:{type:"warning",showIcon:""}},[t._v(t._s(t.$L("\u8BF7\u5148\u4FEE\u6539\u767B\u5F55\u5BC6\u7801\uFF01")))]):t._e(),r("FormItem",{attrs:{label:t.$L("\u65E7\u5BC6\u7801"),prop:"oldpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.oldpass,callback:function(e){t.$set(t.formDatum,"oldpass",e)},expression:"formDatum.oldpass"}})],1),r("FormItem",{attrs:{label:t.$L("\u65B0\u5BC6\u7801"),prop:"newpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.newpass,callback:function(e){t.$set(t.formDatum,"newpass",e)},expression:"formDatum.newpass"}})],1),r("FormItem",{attrs:{label:t.$L("\u786E\u8BA4\u65B0\u5BC6\u7801"),prop:"checkpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.checkpass,callback:function(e){t.$set(t.formDatum,"checkpass",e)},expression:"formDatum.checkpass"}})],1)],1),r("div",{staticClass:"setting-footer"},[r("Button",{attrs:{loading:t.loadIng>0,type:"primary"},on:{click:t.submitForm}},[t._v(t._s(t.$L("\u63D0\u4EA4")))]),r("Button",{staticStyle:{"margin-left":"8px"},attrs:{loading:t.loadIng>0},on:{click:t.resetForm}},[t._v(t._s(t.$L("\u91CD\u7F6E")))])],1)],1)},p=[];const n={data(){return{loadIng:0,formDatum:{oldpass:"",newpass:"",checkpass:""},ruleDatum:{oldpass:[{required:!0,message:this.$L("\u8BF7\u8F93\u5165\u65E7\u5BC6\u7801\uFF01"),trigger:"change"},{type:"string",min:6,message:this.$L("\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D\uFF01"),trigger:"change"}],newpass:[{validator:(t,s,r)=>{s===""?r(new Error(this.$L("\u8BF7\u8F93\u5165\u65B0\u5BC6\u7801\uFF01"))):(this.formDatum.checkpass!==""&&this.$refs.formDatum.validateField("checkpass"),r())},required:!0,trigger:"change"},{type:"string",min:6,message:this.$L("\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D\uFF01"),trigger:"change"}],checkpass:[{validator:(t,s,r)=>{s===""?r(new Error(this.$L("\u8BF7\u91CD\u65B0\u8F93\u5165\u65B0\u5BC6\u7801\uFF01"))):s!==this.formDatum.newpass?r(new Error(this.$L("\u4E24\u6B21\u5BC6\u7801\u8F93\u5165\u4E0D\u4E00\u81F4\uFF01"))):r()},required:!0,trigger:"change"}]}}},computed:{...i(["userInfo","formOptions"])},methods:{submitForm(){this.$refs.formDatum.validate(t=>{t&&(this.loadIng++,this.$store.dispatch("call",{url:"users/editpass",data:this.formDatum}).then(({data:s})=>{$A.messageSuccess("\u4FEE\u6539\u6210\u529F"),this.$store.dispatch("saveUserInfo",s),this.$refs.formDatum.resetFields()}).catch(({msg:s})=>{$A.modalError(s)}).finally(s=>{this.loadIng--}))})},resetForm(){this.$refs.formDatum.resetFields()}}},o={};var l=m(n,a,p,!1,u,null,null,null);function u(t){for(let s in o)this[s]=o[s]}var ot=function(){return l.exports}();export{ot as default};
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n as m}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var p=function(){var r=this,t=r.$createElement,i=r._self._c||t;return i("div")},e=[];const n={},o={};var _=m(n,p,e,!1,s,null,null,null);function s(r){for(let t in o)this[t]=o[t]}var ot=function(){return _.exports}();export{ot as default};
|
||||
import{n as m}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var p=function(){var r=this,t=r.$createElement,i=r._self._c||t;return i("div")},e=[];const n={},o={};var _=m(n,p,e,!1,s,null,null,null);function s(r){for(let t in o)this[t]=o[t]}var ot=function(){return _.exports}();export{ot as default};
|
||||
@ -1 +1 @@
|
||||
import{_ as m}from"./openpgp_hi.15f91b1d.js";import{p}from"./index.40a8e116.js";import{n as e}from"./app.27305e7a.js";import"./jquery.9a8e34a6.js";import"./@babel.9410f858.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var n=function(){var t=this,r=t.$createElement,i=t._self._c||r;return t.ready?i("VPreview",{attrs:{value:t.value}}):i("Loading")},a=[];const s={name:"VMPreview",mixins:[p],components:{VPreview:()=>m(()=>import("./preview.17e43af4.js"),["js/build/preview.17e43af4.js","js/build/preview.15fbcdd9.css","js/build/@kangc.b5fe0a56.js","js/build/@kangc.d8464d83.css","js/build/@babel.9410f858.js","js/build/vue.adba9046.js","js/build/copy-to-clipboard.a53c061d.js","js/build/toggle-selection.d2487283.js","js/build/prismjs.94ec9288.js","js/build/app.27305e7a.js","js/build/app.1bed8789.css","js/build/jquery.9a8e34a6.js","js/build/dayjs.22b500b5.js","js/build/localforage.ff736638.js","js/build/markdown-it.0450edb4.js","js/build/mdurl.ce6c1dd8.js","js/build/uc.micro.8d343c98.js","js/build/entities.48a44fec.js","js/build/linkify-it.c5e8196e.js","js/build/punycode.js.4b3f125a.js","js/build/highlight.js.cbbfb885.js","js/build/markdown-it-link-attributes.e1d5d151.js","js/build/@traptitech.acea8861.js","js/build/vuex.cc7cb26e.js","js/build/openpgp_hi.15f91b1d.js","js/build/axios.37c7f908.js","js/build/mitt.1ea0a2a3.js","js/build/quill-hi.ca2ea0cc.js","js/build/parchment.d5c5924e.js","js/build/quill-delta.385a10bf.js","js/build/fast-diff.f17881f3.js","js/build/lodash.clonedeep.3cc09a31.js","js/build/lodash.isequal.dbdc2157.js","js/build/eventemitter3.78b735ad.js","js/build/lodash-es.76e3a28b.js","js/build/quill-mention-hi.4eeb5a2d.js","js/build/view-design-hi.f1128b4d.js","js/build/html-to-md.f297036e.js","js/build/lodash.8fcd6fd4.js","js/build/vue-router.2d566cd7.js","js/build/vue-clipboard2.fd43a5bc.js","js/build/clipboard.37b37361.js","js/build/vuedraggable.f464b992.js","js/build/sortablejs.3488b922.js","js/build/vue-resize-observer.5af23a43.js","js/build/element-sea.f8a64907.js","js/build/deepmerge.cecf392e.js","js/build/resize-observer-polyfill.5d591c5f.js","js/build/throttle-debounce.7c3948b2.js","js/build/babel-helper-vue-jsx-merge-props.5ed215c3.js","js/build/normalize-wheel.2a034b9f.js","js/build/async-validator.dca2b951.js","js/build/babel-runtime.4773988a.js","js/build/core-js.314b4a1d.js","js/build/index.40a8e116.js"])},data(){return{ready:!1}},async mounted(){await $A.loadScriptS(["js/katex/katex.min.js","js/katex/katex.min.css","js/mermaid.min.js"]),this.ready=!0}},o={};var _=e(s,n,a,!1,l,null,null,null);function l(t){for(let r in o)this[r]=o[r]}var pt=function(){return _.exports}();export{pt as default};
|
||||
import{_ as m}from"./openpgp_hi.15f91b1d.js";import{p}from"./index.40a8e116.js";import{n as e}from"./app.918d02da.js";import"./jquery.b179464f.js";import"./@babel.9410f858.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var n=function(){var t=this,r=t.$createElement,i=t._self._c||r;return t.ready?i("VPreview",{attrs:{value:t.value}}):i("Loading")},a=[];const s={name:"VMPreview",mixins:[p],components:{VPreview:()=>m(()=>import("./preview.577eed06.js"),["js/build/preview.577eed06.js","js/build/preview.15fbcdd9.css","js/build/@kangc.b5fe0a56.js","js/build/@kangc.d8464d83.css","js/build/@babel.9410f858.js","js/build/vue.adba9046.js","js/build/copy-to-clipboard.a53c061d.js","js/build/toggle-selection.d2487283.js","js/build/prismjs.94ec9288.js","js/build/app.918d02da.js","js/build/app.4f9fe422.css","js/build/jquery.b179464f.js","js/build/dayjs.7f198189.js","js/build/localforage.578a5228.js","js/build/markdown-it.0450edb4.js","js/build/mdurl.ce6c1dd8.js","js/build/uc.micro.8d343c98.js","js/build/entities.48a44fec.js","js/build/linkify-it.c5e8196e.js","js/build/punycode.js.4b3f125a.js","js/build/highlight.js.cbbfb885.js","js/build/markdown-it-link-attributes.e1d5d151.js","js/build/@traptitech.acea8861.js","js/build/vuex.cc7cb26e.js","js/build/openpgp_hi.15f91b1d.js","js/build/axios.37c7f908.js","js/build/mitt.1ea0a2a3.js","js/build/quill-hi.ca2ea0cc.js","js/build/parchment.d5c5924e.js","js/build/quill-delta.385a10bf.js","js/build/fast-diff.f17881f3.js","js/build/lodash.clonedeep.3cc09a31.js","js/build/lodash.isequal.dbdc2157.js","js/build/eventemitter3.78b735ad.js","js/build/lodash-es.76e3a28b.js","js/build/quill-mention-hi.4eeb5a2d.js","js/build/view-design-hi.f1128b4d.js","js/build/html-to-md.f297036e.js","js/build/lodash.8fcd6fd4.js","js/build/vue-router.2d566cd7.js","js/build/vue-clipboard2.fd43a5bc.js","js/build/clipboard.37b37361.js","js/build/vuedraggable.f464b992.js","js/build/sortablejs.3488b922.js","js/build/vue-resize-observer.5af23a43.js","js/build/element-sea.f8a64907.js","js/build/deepmerge.cecf392e.js","js/build/resize-observer-polyfill.5d591c5f.js","js/build/throttle-debounce.7c3948b2.js","js/build/babel-helper-vue-jsx-merge-props.5ed215c3.js","js/build/normalize-wheel.2a034b9f.js","js/build/async-validator.dca2b951.js","js/build/babel-runtime.4773988a.js","js/build/core-js.314b4a1d.js","js/build/index.40a8e116.js"])},data(){return{ready:!1}},async mounted(){await $A.loadScriptS(["js/katex/katex.min.js","js/katex/katex.min.css","js/mermaid.min.js"]),this.ready=!0}},o={};var _=e(s,n,a,!1,l,null,null,null);function l(t){for(let r in o)this[r]=o[r]}var pt=function(){return _.exports}();export{pt as default};
|
||||
@ -1 +1 @@
|
||||
import{V as e,d as p,a as s,b as n,c as a,_ as l,e as u,v as _}from"./@kangc.b5fe0a56.js";import{P as c}from"./prismjs.94ec9288.js";import{l as v,u as o,n as d}from"./app.27305e7a.js";import{p as f}from"./index.40a8e116.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./copy-to-clipboard.a53c061d.js";import"./toggle-selection.d2487283.js";import"./jquery.9a8e34a6.js";import"./dayjs.22b500b5.js";import"./localforage.ff736638.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var h=function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"vmpreview-wrapper",on:{click:t.handleClick}},[i("v-md-preview",{attrs:{text:t.previewContent}})],1)},g=[];/^zh/.test(v)?e.lang.use("zh-CN",p):e.lang.use("en-US",s);e.use(n());e.use(a());e.use(l());e.use(u());const w={mixins:[f],components:{[e.name]:e},created(){e.use(_,{Prism:c,extend(t){o.initReasoningPlugin(t)}})},computed:{previewContent({value:t}){return o.clearEmptyReasoning(t)}},methods:{handleClick({target:t}){if(t.nodeName==="IMG"){const r=[...this.$el.querySelectorAll("img").values()].map(i=>i.src);if(r.length===0)return;this.$store.dispatch("previewImage",{index:t.src,list:r})}}}},m={};var x=d(w,h,g,!1,C,"6797ab07",null,null);function C(t){for(let r in m)this[r]=m[r]}var wt=function(){return x.exports}();export{wt as default};
|
||||
import{V as e,d as p,a as s,b as n,c as a,_ as l,e as u,v as _}from"./@kangc.b5fe0a56.js";import{P as c}from"./prismjs.94ec9288.js";import{l as v,u as o,n as d}from"./app.918d02da.js";import{p as f}from"./index.40a8e116.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./copy-to-clipboard.a53c061d.js";import"./toggle-selection.d2487283.js";import"./jquery.b179464f.js";import"./dayjs.7f198189.js";import"./localforage.578a5228.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var h=function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"vmpreview-wrapper",on:{click:t.handleClick}},[i("v-md-preview",{attrs:{text:t.previewContent}})],1)},g=[];/^zh/.test(v)?e.lang.use("zh-CN",p):e.lang.use("en-US",s);e.use(n());e.use(a());e.use(l());e.use(u());const w={mixins:[f],components:{[e.name]:e},created(){e.use(_,{Prism:c,extend(t){o.initReasoningPlugin(t)}})},computed:{previewContent({value:t}){return o.clearEmptyReasoning(t)}},methods:{handleClick({target:t}){if(t.nodeName==="IMG"){const r=[...this.$el.querySelectorAll("img").values()].map(i=>i.src);if(r.length===0)return;this.$store.dispatch("previewImage",{index:t.src,list:r})}}}},m={};var x=d(w,h,g,!1,C,"6797ab07",null,null);function C(t){for(let r in m)this[r]=m[r]}var wt=function(){return x.exports}();export{wt as default};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user