mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-20 00:01:54 +00:00
Compare commits
87 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f2afac6ead | ||
![]() |
0dd460a6d7 | ||
![]() |
57bf79ec56 | ||
![]() |
8cfdd43ee1 | ||
![]() |
21f245ddaf | ||
![]() |
00a19769e1 | ||
![]() |
d2a22763de | ||
![]() |
da75e34b95 | ||
![]() |
08a564588c | ||
![]() |
a639d65ed6 | ||
![]() |
b42314b29e | ||
![]() |
17c93aa3fc | ||
![]() |
5b4a1a5581 | ||
![]() |
d82f78a026 | ||
![]() |
21e5d68994 | ||
![]() |
a6a9c149dc | ||
![]() |
61c11abda2 | ||
![]() |
3a11b51729 | ||
![]() |
6159d0ba9a | ||
![]() |
bf2ab87c44 | ||
![]() |
ff1e2871d4 | ||
![]() |
2d9f3d8216 | ||
![]() |
e7a9b2317d | ||
![]() |
0ab02d032a | ||
![]() |
ab8622c9fd | ||
![]() |
70a12e36e1 | ||
![]() |
9706108185 | ||
![]() |
5138873f43 | ||
![]() |
d8625d6b9f | ||
![]() |
ec4f200435 | ||
![]() |
91f3b78b80 | ||
![]() |
3b321858d2 | ||
![]() |
0814d44207 | ||
![]() |
04010b58bb | ||
![]() |
a68dc35a23 | ||
![]() |
40c4f57b37 | ||
![]() |
652084a140 | ||
![]() |
c29d5bc98a | ||
![]() |
42b5d0824a | ||
![]() |
7226b09214 | ||
![]() |
b97053daee | ||
![]() |
d5d6a486d2 | ||
![]() |
889ab0f147 | ||
![]() |
e6a1f98cf1 | ||
![]() |
5f81a47db9 | ||
![]() |
5d16bb99c7 | ||
![]() |
f24439d580 | ||
![]() |
6d3334543e | ||
![]() |
62019e3ac1 | ||
![]() |
adc9bce844 | ||
![]() |
26b70447da | ||
![]() |
832e408952 | ||
![]() |
b64c2ae14e | ||
![]() |
e63d296457 | ||
![]() |
909c99d1c0 | ||
![]() |
cbd8609c97 | ||
![]() |
b8c1b5e428 | ||
![]() |
cb1b0ac669 | ||
![]() |
bbe7d2186e | ||
![]() |
d0469685d9 | ||
![]() |
32ecd48375 | ||
![]() |
33a715c8c3 | ||
![]() |
eba3d2709e | ||
![]() |
b3af7184e7 | ||
![]() |
54411e59ad | ||
![]() |
f9da76f07e | ||
![]() |
af0ecd2400 | ||
![]() |
f3f730b32e | ||
![]() |
076b1c7129 | ||
![]() |
3298840454 | ||
![]() |
b0c300be43 | ||
![]() |
e86b3cde6f | ||
![]() |
ff55d3d1ef | ||
![]() |
e024390028 | ||
![]() |
c314e4638d | ||
![]() |
bcd2cc50ec | ||
![]() |
8e1f6f894f | ||
![]() |
eab8841cca | ||
![]() |
a78f3399fd | ||
![]() |
44cbc9298f | ||
![]() |
87ad435a13 | ||
![]() |
9e3961b83d | ||
![]() |
5a7b5346ae | ||
![]() |
e4037d5aac | ||
![]() |
6998a61f37 | ||
![]() |
4986730f44 | ||
![]() |
824f995209 |
@@ -496,6 +496,69 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jaimeadf",
|
||||
"name": "Marmota",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/40345645?v=4",
|
||||
"profile": "https://discord.gg/4c5EVTBhtp",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "TENX-S",
|
||||
"name": "Ares Andrew",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/40336192?v=4",
|
||||
"profile": "https://ares.zone",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "gkor",
|
||||
"name": "George Korsnick",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/780052?v=4",
|
||||
"profile": "https://usual.io/",
|
||||
"contributions": [
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "uluhonolulu",
|
||||
"name": "Artem Smirnov",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/872764?v=4",
|
||||
"profile": "https://about.me/ulu",
|
||||
"contributions": [
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nevotheless",
|
||||
"name": "Tim Kopplow",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/779797?v=4",
|
||||
"profile": "https://github.com/nevotheless",
|
||||
"contributions": [
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mrthock",
|
||||
"name": "mrthock",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/88901709?v=4",
|
||||
"profile": "https://github.com/mrthock",
|
||||
"contributions": [
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lrottach",
|
||||
"name": "Lukas Rottach",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/50323692?v=4",
|
||||
"profile": "https://github.com/lrottach",
|
||||
"contributions": [
|
||||
"financial"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.5.0
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.5.0
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2.5.0
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
|
||||
- name: Upload packages to packagecloud.io
|
||||
uses: TykTechnologies/packagecloud-action@main
|
||||
uses: Eugeny/packagecloud-action@main
|
||||
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}
|
||||
@@ -253,7 +253,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.5.0
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.5.0
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -34,3 +34,5 @@ sentry-symbols.js
|
||||
|
||||
tabby-ssh/util/pagent.exe
|
||||
*.psd
|
||||
|
||||
crowdin.yml
|
||||
|
13
README.md
13
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge"></a> <a href="https://nightly.link/Eugeny/tabby/workflows/build/master"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> <a href="https://matrix.to/#/#tabby-general:matrix.org"><img alt="Matrix" src="https://img.shields.io/matrix/tabby-general:matrix.org?logo=matrix&style=for-the-badge&color=magenta"></a> <a href="https://twitter.com/eugeeeeny"><img alt="Twitter" src="https://shields.io/badge/Subscribe-News-blue?logo=twitter&style=for-the-badge&color=blue"></a>
|
||||
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge"></a> <a href="https://nightly.link/Eugeny/tabby/workflows/build/master"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> <a href="https://matrix.to/#/#tabby-general:matrix.org"><img alt="Matrix" src="https://img.shields.io/matrix/tabby-general:matrix.org?logo=matrix&style=for-the-badge&color=magenta"></a>   <a href="https://translate.tabby.sh/"><img alt="Translate" src="https://shields.io/badge/Translate-UI-white?logo=googletranslate&style=for-the-badge&color=white&logoColor=fff"></a> <a href="https://twitter.com/eugeeeeny"><img alt="Twitter" src="https://shields.io/badge/Subscribe-News-blue?logo=twitter&style=for-the-badge&color=blue"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<br/>
|
||||
<p align="center">
|
||||
This README is also available in: <a href="./README.ko-KR.md">Korean</a>
|
||||
This README is also available in: <a href="./README.ko-KR.md">Korean</a> <a href="./README.zh-CN.md">简体中文</a>
|
||||
</p>
|
||||
|
||||
----
|
||||
@@ -218,6 +218,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center"><a href="https://github.com/composer404"><img src="https://avatars.githubusercontent.com/u/58251560?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Przemyslaw Kozik</b></sub></a><br /><a href="#design-composer404" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/highfredo"><img src="https://avatars.githubusercontent.com/u/5951524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alfredo Arellano de la Fuente</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=highfredo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/NessunKim"><img src="https://avatars.githubusercontent.com/u/12974079?v=4?s=100" width="100px;" alt=""/><br /><sub><b>MH Kim</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=NessunKim" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://discord.gg/4c5EVTBhtp"><img src="https://avatars.githubusercontent.com/u/40345645?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marmota</b></sub></a><br /><a href="#design-jaimeadf" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://ares.zone"><img src="https://avatars.githubusercontent.com/u/40336192?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ares Andrew</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=TENX-S" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://usual.io/"><img src="https://avatars.githubusercontent.com/u/780052?v=4?s=100" width="100px;" alt=""/><br /><sub><b>George Korsnick</b></sub></a><br /><a href="#financial-gkor" title="Financial">💵</a></td>
|
||||
<td align="center"><a href="https://about.me/ulu"><img src="https://avatars.githubusercontent.com/u/872764?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Artem Smirnov</b></sub></a><br /><a href="#financial-uluhonolulu" title="Financial">💵</a></td>
|
||||
<td align="center"><a href="https://github.com/nevotheless"><img src="https://avatars.githubusercontent.com/u/779797?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Kopplow</b></sub></a><br /><a href="#financial-nevotheless" title="Financial">💵</a></td>
|
||||
<td align="center"><a href="https://github.com/mrthock"><img src="https://avatars.githubusercontent.com/u/88901709?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mrthock</b></sub></a><br /><a href="#financial-mrthock" title="Financial">💵</a></td>
|
||||
<td align="center"><a href="https://github.com/lrottach"><img src="https://avatars.githubusercontent.com/u/50323692?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lukas Rottach</b></sub></a><br /><a href="#financial-lrottach" title="Financial">💵</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
226
README.zh-CN.md
Normal file
226
README.zh-CN.md
Normal file
@@ -0,0 +1,226 @@
|
||||
[](https://tabby.sh)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge"></a> <a href="https://nightly.link/Eugeny/tabby/workflows/build/master"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> <a href="https://matrix.to/#/#tabby-general:matrix.org"><img alt="Matrix" src="https://img.shields.io/matrix/tabby-general:matrix.org?logo=matrix&style=for-the-badge&color=magenta"></a> <a href="https://twitter.com/eugeeeeny"><img alt="Twitter" src="https://shields.io/badge/Subscribe-News-blue?logo=twitter&style=for-the-badge&color=blue"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://ko-fi.com/J3J8KWTF">
|
||||
<img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=2" width="150">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
----
|
||||
|
||||
### 下载
|
||||
|
||||
* [Latest release](https://github.com/Eugeny/tabby/releases/latest)
|
||||
* [Repositories](https://packagecloud.io/eugeny/tabby): [Debian/Ubuntu-based](https://packagecloud.io/eugeny/tabby/install#bash-deb), [RPM-based](https://packagecloud.io/eugeny/tabby/install#bash-rpm)
|
||||
* [Latest nightly build](https://nightly.link/Eugeny/tabby/workflows/build/master)
|
||||
|
||||
----
|
||||
|
||||
[**Tabby**](https://tabby.sh) (前身是 **Terminus**) 是一个可高度配置的终端模拟器和 SSH 或串口客户端,支持 Windows,macOS 和 Linux
|
||||
|
||||
* 集成 SSH,Telnet 客户端和连接管理器
|
||||
* 集成串行终端
|
||||
* 定制主题和配色方案
|
||||
* 完全可配置的快捷键和多键快捷键
|
||||
* 拆分窗格
|
||||
* 记住你的标签
|
||||
* 支持 PowerShell(和 PS Core)、WSL、Git-Bash、Cygwin、MSYS2、Cmder 和 CMD
|
||||
* 在 SSH 会话中通过 Zmodem 进行直接文件传输
|
||||
* 完整的 Unicode 支持,包括双角字符
|
||||
* 不会因快速的输出而卡住
|
||||
* Windows 上的正确 shell 体验,包括 tab 自动补全(通过 Clink)
|
||||
* Integrated encrypted container for SSH secrets and configuration
|
||||
* SSH、SFTP 和 Telnet 客户端可用作 [Web 应用程序](https://tabby.sh/app)(也可[托管](https://github.com/Eugeny/tabby-web))
|
||||
|
||||
# 目录 <!-- omit in toc -->
|
||||
|
||||
- [Tabby的正确用途](#tabby的正确用途)
|
||||
- [终端特性](#终端特性)
|
||||
- [SSH 客户端](#ssh-客户端)
|
||||
- [串行终端](#串行终端)
|
||||
- [可移植的](#可移植的)
|
||||
- [插件](#插件)
|
||||
- [主题](#主题)
|
||||
- [贡献](#贡献)
|
||||
|
||||
<a name="about"></a>
|
||||
|
||||
# Tabby的正确用途
|
||||
|
||||
* **Tabby 是** Windows 标准终端 (conhost)、PowerShell ISE、PuTTY、macOS Terminal.app 和 iTerm 的替代品
|
||||
|
||||
* **Tabby 不是** Tabby 不是新的 shell,也不是 MinGW 或 Cygwin 的替代品。它也不是轻量级的 - 如果,请考虑 [Conemu](https://conemu.github.io) 或 [Alacritty](https://github.com/jwilm/alacritty)
|
||||
<a name="terminal"></a>
|
||||
|
||||
# 终端特性
|
||||
|
||||

|
||||
|
||||
* 一个 V220 终端 + 各种插件
|
||||
* 多个嵌套的拆分窗格
|
||||
* Tabs on any side of the window
|
||||
* 带有全局生成热键的可选可停靠窗口(“Quake console”)
|
||||
* 进度检测
|
||||
* 流程完成通知
|
||||
* 括号粘贴,多行粘贴警告
|
||||
* 连体字
|
||||
* 自定义 shell 配置文件
|
||||
* 可选的 RMB 粘贴和复制选择(PuTTY 风格)
|
||||
|
||||
<a name="ssh"></a>
|
||||
# SSH 客户端
|
||||
|
||||

|
||||
|
||||
* 带有连接管理器的 SSH2 客户端
|
||||
* X11和端口转发
|
||||
* 自动跳转主机管理
|
||||
* 代理转发(包括 Pageant 和 Windows 原生 OpenSSH 代理)
|
||||
* 登录脚本
|
||||
|
||||
<a name="serial"></a>
|
||||
# 串行终端
|
||||
|
||||
* 保存链接
|
||||
* Readline 输入支持
|
||||
* 可选的十六进制逐字节输入和十六进制转储输出
|
||||
* 换行转换
|
||||
* 自动重连
|
||||
|
||||
<a name="portable"></a>
|
||||
# 可移植的
|
||||
|
||||
如果在 Tabby.exe 所在的同一位置创建数据文件夹,Tabby 将在 Windows 上作为便携式的应用程序运行。
|
||||
|
||||
<a name="plugins"></a>
|
||||
# 插件
|
||||
|
||||
插件和主题可以直接从 Tabby 中的设置视图安装。
|
||||
|
||||
* [clickable-links](https://github.com/Eugeny/tabby-clickable-links) - 使终端中的路径和 URL 可点击
|
||||
* [docker](https://github.com/Eugeny/tabby-docker) - 连接到 Docker 容器
|
||||
* [title-control](https://github.com/kbjr/terminus-title-control) - 允许通过提供要删除的前缀、后缀和/或字符串来修改终端选项卡的标题
|
||||
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - 快速向一个或所有终端选项卡发送命令
|
||||
* [save-output](https://github.com/Eugeny/tabby-save-output) - 将终端输出记录到文件中
|
||||
* [sync-config](https://github.com/starxg/terminus-sync-config) - 将配置同步到 Gist 或 Gitee
|
||||
* [clippy](https://github.com/Eugeny/tabby-clippy) - 一个一直打扰你的示例插件
|
||||
* [workspace-manager](https://github.com/composer404/tabby-workspace-manager) - 允许根据给定的配置创建自定义工作区配置文件
|
||||
* [search-in-browser](https://github.com/composer404/tabby-search-in-browser) - 使用从 Tabby 选项卡中选择的文本打开默认系统浏览器
|
||||
|
||||
<a name="themes"></a>
|
||||
# 主题
|
||||
|
||||
* [hype](https://github.com/Eugeny/tabby-theme-hype) - 受 Hyper 启发的主题
|
||||
* [relaxed](https://github.com/Relaxed-Theme/relaxed-terminal-themes#terminus) - 为 Tabby 打造的 Relaxed 主题
|
||||
* [gruvbox](https://github.com/porkloin/terminus-theme-gruvbox)
|
||||
* [windows10](https://www.npmjs.com/package/terminus-theme-windows10)
|
||||
* [altair](https://github.com/yxuko/terminus-altair)
|
||||
|
||||
# Sponsors <!-- omit in toc -->
|
||||
|
||||
[](https://packagecloud.io)
|
||||
|
||||
[**packagecloud**](https://packagecloud.io) 提供了免费的 Debian/RPM 存储库托管
|
||||
|
||||
<a name="contributing"></a>
|
||||
# 贡献
|
||||
|
||||
欢迎提交 PR 和插件!
|
||||
|
||||
请参阅 [HACKING.md](https://github.com/Eugeny/tabby/blob/master/HACKING.md) 和 [API 文档](https://docs.tabby.sh/) 以获取有关项目布局的信息以及非常简短的插件开发教程。
|
||||
|
||||
---
|
||||
<a name="contributors"></a>
|
||||
|
||||
感谢这些人,他们棒极了!([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.russellmyers.com"><img src="https://avatars2.githubusercontent.com/u/184085?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Russell Myers</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mezner" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.morwire.com"><img src="https://avatars1.githubusercontent.com/u/3991658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austin Warren</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ehwarren" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Drachenkaetzchen"><img src="https://avatars1.githubusercontent.com/u/162974?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Felicia Hummel</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Drachenkaetzchen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mikemaccana"><img src="https://avatars2.githubusercontent.com/u/172594?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mike MacCana</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mikemaccana" title="Tests">⚠️</a> <a href="#design-mikemaccana" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/yxuko"><img src="https://avatars1.githubusercontent.com/u/1786317?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yacine Kanzari</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=yxuko" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/BBJip"><img src="https://avatars2.githubusercontent.com/u/32908927?v=4?s=100" width="100px;" alt=""/><br /><sub><b>BBJip</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=BBJip" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Futagirl"><img src="https://avatars2.githubusercontent.com/u/33533958?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Futagirl</b></sub></a><br /><a href="#design-Futagirl" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.levrik.io"><img src="https://avatars3.githubusercontent.com/u/9491603?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Levin Rickert</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=levrik" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://kwonoj.github.io"><img src="https://avatars2.githubusercontent.com/u/1210596?v=4?s=100" width="100px;" alt=""/><br /><sub><b>OJ Kwon</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=kwonoj" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Domain"><img src="https://avatars2.githubusercontent.com/u/903197?v=4?s=100" width="100px;" alt=""/><br /><sub><b>domain</b></sub></a><br /><a href="#plugin-Domain" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/tabby/commits?author=Domain" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jbrumond.me"><img src="https://avatars1.githubusercontent.com/u/195127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>James Brumond</b></sub></a><br /><a href="#plugin-kbjr" title="Plugin/utility libraries">🔌</a></td>
|
||||
<td align="center"><a href="http://www.growingwiththeweb.com"><img src="https://avatars0.githubusercontent.com/u/2193314?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Imms</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Tyriar" title="Code">💻</a> <a href="#plugin-Tyriar" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/tabby/commits?author=Tyriar" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/baflo"><img src="https://avatars2.githubusercontent.com/u/834350?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Florian Bachmann</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=baflo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://michael-kuehnel.de"><img src="https://avatars2.githubusercontent.com/u/441011?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Kühnel</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mischah" title="Code">💻</a> <a href="#design-mischah" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/NieLeben"><img src="https://avatars3.githubusercontent.com/u/47182955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tilmann Meyer</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=NieLeben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jubeat.net"><img src="https://avatars3.githubusercontent.com/u/11289158?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/issues?q=author%3APMExtra" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://jjuhas.keybase.pub//"><img src="https://avatars1.githubusercontent.com/u/6438760?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonathan</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=IgnusG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://hans-koch.me"><img src="https://avatars0.githubusercontent.com/u/1093709?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hans Koch</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=hammster" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://thepuzzlemaker.info"><img src="https://avatars3.githubusercontent.com/u/12666617?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dak Smyth</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ThePuzzlemaker" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://yfwz100.github.io"><img src="https://avatars2.githubusercontent.com/u/983211?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Wang Zhi</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=yfwz100" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jack1142"><img src="https://avatars0.githubusercontent.com/u/6032823?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jack1142</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=jack1142" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hdougie"><img src="https://avatars1.githubusercontent.com/u/450799?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Howie Douglas</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=hdougie" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://chriskaczor.com"><img src="https://avatars2.githubusercontent.com/u/180906?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ckaczor" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.boxmein.net"><img src="https://avatars1.githubusercontent.com/u/358714?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Johannes Kadak</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=boxmein" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/LeSeulArtichaut"><img src="https://avatars1.githubusercontent.com/u/38361244?v=4?s=100" width="100px;" alt=""/><br /><sub><b>LeSeulArtichaut</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=LeSeulArtichaut" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/CyrilTaylor"><img src="https://avatars0.githubusercontent.com/u/12631466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cyril Taylor</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=CyrilTaylor" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nstefanou"><img src="https://avatars3.githubusercontent.com/u/51129173?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nstefanou</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=nstefanou" title="Code">💻</a> <a href="#plugin-nstefanou" title="Plugin/utility libraries">🔌</a></td>
|
||||
<td align="center"><a href="https://github.com/orin220444"><img src="https://avatars3.githubusercontent.com/u/30747229?v=4?s=100" width="100px;" alt=""/><br /><sub><b>orin220444</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=orin220444" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Goobles"><img src="https://avatars3.githubusercontent.com/u/8776771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gobius Dolhain</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Goobles" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/3l0w"><img src="https://avatars2.githubusercontent.com/u/37798980?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gwilherm Folliot</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=3l0w" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Dimitory"><img src="https://avatars0.githubusercontent.com/u/475955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dmitry Pronin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=dimitory" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/JonathanBeverley"><img src="https://avatars1.githubusercontent.com/u/20328966?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonathan Beverley</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=JonathanBeverley" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/zend"><img src="https://avatars1.githubusercontent.com/u/25160?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zenghai Liang</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=zend" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://about.me/matishadow"><img src="https://avatars0.githubusercontent.com/u/9083085?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mateusz Tracz</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=matishadow" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://zergpool.com"><img src="https://avatars3.githubusercontent.com/u/36234677?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pinpin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=pinpins" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/TakuroOnoda"><img src="https://avatars0.githubusercontent.com/u/1407926?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Takuro Onoda</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=TakuroOnoda" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/frauhottelmann"><img src="https://avatars2.githubusercontent.com/u/902705?v=4?s=100" width="100px;" alt=""/><br /><sub><b>frauhottelmann</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=frauhottelmann" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://patalong.pl"><img src="https://avatars.githubusercontent.com/u/29167842?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotr Patalong</b></sub></a><br /><a href="#design-VectorKappa" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/clarkwang"><img src="https://avatars.githubusercontent.com/u/157076?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Clark Wang</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=clarkwang" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/iamchating"><img src="https://avatars.githubusercontent.com/u/7088153?v=4?s=100" width="100px;" alt=""/><br /><sub><b>iamchating</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=iamchating" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/starxg"><img src="https://avatars.githubusercontent.com/u/34997494?v=4?s=100" width="100px;" alt=""/><br /><sub><b>starxg</b></sub></a><br /><a href="#plugin-starxg" title="Plugin/utility libraries">🔌</a></td>
|
||||
<td align="center"><a href="http://hashnote.net/"><img src="https://avatars.githubusercontent.com/u/546312?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alisue</b></sub></a><br /><a href="#design-lambdalisue" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ydcool"><img src="https://avatars.githubusercontent.com/u/5668295?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dominic Yin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ydcool" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bdr99"><img src="https://avatars.githubusercontent.com/u/2292715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brandon Rothweiler</b></sub></a><br /><a href="#design-bdr99" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://git.io/JnP49"><img src="https://avatars.githubusercontent.com/u/63876444?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Logic Machine</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=logicmachine123" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/cypherbits"><img src="https://avatars.githubusercontent.com/u/10424900?v=4?s=100" width="100px;" alt=""/><br /><sub><b>cypherbits</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=cypherbits" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://modulolotus.net"><img src="https://avatars.githubusercontent.com/u/946421?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matthew Davidson</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=KingMob" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/al-wi"><img src="https://avatars.githubusercontent.com/u/11092199?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexander Wiedemann</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=al-wi" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.notion.so/3d45c6bd2cbd4f938873a4bd12e23375"><img src="https://avatars.githubusercontent.com/u/59506394?v=4?s=100" width="100px;" alt=""/><br /><sub><b>장보연</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=BoYeonJang" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Me1onRind"><img src="https://avatars.githubusercontent.com/u/19531270?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zZ</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Me1onRind" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tainoNZ"><img src="https://avatars.githubusercontent.com/u/49261322?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aaron Davison</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=tainoNZ" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/composer404"><img src="https://avatars.githubusercontent.com/u/58251560?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Przemyslaw Kozik</b></sub></a><br /><a href="#design-composer404" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/highfredo"><img src="https://avatars.githubusercontent.com/u/5951524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alfredo Arellano de la Fuente</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=highfredo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/NessunKim"><img src="https://avatars.githubusercontent.com/u/12974079?v=4?s=100" width="100px;" alt=""/><br /><sub><b>MH Kim</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=NessunKim" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://discord.gg/4c5EVTBhtp"><img src="https://avatars.githubusercontent.com/u/40345645?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marmota</b></sub></a><br /><a href="#design-jaimeadf" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
<img src="https://ga-beacon.appspot.com/UA-3278102-18/github/readme" width="1"/>
|
@@ -70,10 +70,10 @@ export class Application {
|
||||
app.commandLine.appendSwitch('enable-transparent-visuals')
|
||||
app.disableHardwareAcceleration()
|
||||
}
|
||||
if (this.configStore.hacks?.disableGPU) {
|
||||
app.commandLine.appendSwitch('disable-gpu')
|
||||
app.disableHardwareAcceleration()
|
||||
}
|
||||
}
|
||||
if (this.configStore.hacks?.disableGPU) {
|
||||
app.commandLine.appendSwitch('disable-gpu')
|
||||
app.disableHardwareAcceleration()
|
||||
}
|
||||
|
||||
this.userPluginsPath = path.join(
|
||||
|
@@ -30,15 +30,15 @@
|
||||
"mz": "^2.7.0",
|
||||
"native-process-working-directory": "^1.0.2",
|
||||
"npm": "6",
|
||||
"rxjs": "^7.4.0",
|
||||
"rxjs": "^7.5.1",
|
||||
"source-map-support": "^0.5.20",
|
||||
"v8-compile-cache": "^2.3.0",
|
||||
"yargs": "^17.2.1"
|
||||
"yargs": "^17.3.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tabby-gang/windows-blurbehind": "^3.0.0",
|
||||
"macos-native-processlist": "^2.0.0",
|
||||
"serialport": "^10.0.0",
|
||||
"serialport": "^10.0.1",
|
||||
"windows-native-registry": "^3.1.0",
|
||||
"windows-process-tree": "^0.3.2"
|
||||
},
|
||||
|
@@ -1,14 +0,0 @@
|
||||
diff --git a/node_modules/@serialport/bindings/src/serialport.cpp b/node_modules/@serialport/bindings/src/serialport.cpp
|
||||
index c48e150..00a5f5a 100644
|
||||
--- a/node_modules/@serialport/bindings/src/serialport.cpp
|
||||
+++ b/node_modules/@serialport/bindings/src/serialport.cpp
|
||||
@@ -269,7 +269,8 @@ Napi::Value Drain(const Napi::CallbackInfo& info) {
|
||||
}
|
||||
|
||||
inline SerialPortParity ToParityEnum(const Napi::String& napistr) {
|
||||
- const char* str = napistr.Utf8Value().c_str();
|
||||
+ auto tmp = napistr.Utf8Value();
|
||||
+ const char* str = tmp.c_str();
|
||||
size_t count = strlen(str);
|
||||
SerialPortParity parity = SERIALPORT_PARITY_NONE;
|
||||
if (!strncasecmp(str, "none", count)) {
|
189
app/yarn.lock
189
app/yarn.lock
@@ -25,74 +25,74 @@
|
||||
update-notifier "^2.2.0"
|
||||
yargs "^8.0.2"
|
||||
|
||||
"@serialport/binding-abstract@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/binding-abstract/-/binding-abstract-10.0.0.tgz#c5aea29de3721de80640e290f52217d00d927e4e"
|
||||
integrity sha512-1IwOMDOWqKO0csrTOv95Ah0Av012DZB8C0OF11SmE3eyh8ab1+y4/Yah/8byMAMG7TXw+2LqkNs1oZtOJGlY1Q==
|
||||
"@serialport/binding-abstract@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz#fc5a69b05d364fa7db872b3fe64ae85aaa3030a5"
|
||||
integrity sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==
|
||||
dependencies:
|
||||
debug "^4.3.2"
|
||||
|
||||
"@serialport/binding-mock@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/binding-mock/-/binding-mock-10.0.0.tgz#dc850c3e06f2be4b0c5e0461d2e12a2506cca573"
|
||||
integrity sha512-X+lJqU/GbXxxqA4b3T+YK9vQLtoNSjRF+hz1gyYHpNxDFX0dSLU1OVnQUZ2Zi8aa6IBdk/4DcuGN0tDNAlmtKg==
|
||||
"@serialport/binding-mock@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/binding-mock/-/binding-mock-10.0.1.tgz#b70747c897b861fe7d090f0350f0dc5a37f36b70"
|
||||
integrity sha512-cU+UtCaQI1ZOWzIa4uT7Z0ymgAyQMSwEBF/BM87LtQ9QFjLwCgmuouy3vcsryWNEN3Lg0GwhQzl9ZuDw4bs/qw==
|
||||
dependencies:
|
||||
"@serialport/binding-abstract" "10.0.0"
|
||||
"@serialport/binding-abstract" "10.0.1"
|
||||
debug "^4.3.2"
|
||||
|
||||
"@serialport/bindings@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/bindings/-/bindings-10.0.0.tgz#718b105adae977886967ab0edc68a067be9183f8"
|
||||
integrity sha512-t415A6clhsUX0dBRzN0NRN7Yb6y9U1jA4oGzxL2fWCy7XxQq8beI0GnMebEUaZDWKZ7IAVwGnAPUunk7QdlWlA==
|
||||
"@serialport/bindings@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/bindings/-/bindings-10.0.1.tgz#b8f1d81dae370b954329ec9fdbabb23df74e6a35"
|
||||
integrity sha512-CcSE0OQQwpEup0LebG8bMFhVv+MB2wOm2yHWrdY6UiP3AEh7bB8F6sU1B/iq78BogyoIQ3ZDZBEi4I4F1hYVvA==
|
||||
dependencies:
|
||||
"@serialport/binding-abstract" "10.0.0"
|
||||
"@serialport/parser-readline" "10.0.0"
|
||||
"@serialport/binding-abstract" "10.0.1"
|
||||
"@serialport/parser-readline" "10.0.1"
|
||||
bindings "^1.5.0"
|
||||
debug "^4.3.2"
|
||||
node-addon-api "4.2.0"
|
||||
prebuild-install "^7.0.0"
|
||||
|
||||
"@serialport/parser-byte-length@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-byte-length/-/parser-byte-length-10.0.0.tgz#05ca2721a50dcdc93f68f3f1e72e10fd82362e84"
|
||||
integrity sha512-QmZw7oTt6LBHBFbMIPLIZM4WuXpMeK5EpCpXKFtw4a7+yF0yLPwz6uSV2Cf3SvunCfI3eWvucMKpgYNOvglsgA==
|
||||
"@serialport/parser-byte-length@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-byte-length/-/parser-byte-length-10.0.1.tgz#c9038449e82e2f36093e7d3efe3ca16c6e357f3d"
|
||||
integrity sha512-uOQa0KEGT7IIGSWCN53NE5ZYaWoeeGLDCSX+ssDadyQxy47hMHuP/JotdWqHg7lDwxUHe0tDl4SOEeEnDx1l6A==
|
||||
|
||||
"@serialport/parser-cctalk@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-cctalk/-/parser-cctalk-10.0.0.tgz#de42e6952c086da3e384756502f9aaca0e97f881"
|
||||
integrity sha512-jCxzY2IPghNMaJ+GsUgSOoPCI2v1FZg7RvpSJ/b/igK+M/z/p7oRyWf1LqMyjZT7rP4Ha8ZfsQQy5qGzQ3kuzw==
|
||||
"@serialport/parser-cctalk@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-cctalk/-/parser-cctalk-10.0.1.tgz#df3c26886e920a45e17aba563b44324f5c1906b8"
|
||||
integrity sha512-boVr8akjX/7iCtMHeFT16ek4m0/oV9YA6A2mstVCpKle2op42qByx3jY5RzQ52c13oQvq1E6tG0lWJrzdTK+Yw==
|
||||
|
||||
"@serialport/parser-delimiter@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-delimiter/-/parser-delimiter-10.0.0.tgz#43eb2e3b4c4cf8633eb1200b3af5488ba9a7ecb6"
|
||||
integrity sha512-s7j+RIxxmyb7xJ3WVcf+IfjFqyoh7k7Edqwqvk2sQLU6UOBNAktNQDHIM/vksQ9QkjJjvl8rIo0YznfBkWbZOQ==
|
||||
"@serialport/parser-delimiter@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz#bb0a04e140bf428d5b49e7838b9f136c40b4a091"
|
||||
integrity sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==
|
||||
|
||||
"@serialport/parser-inter-byte-timeout@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.0.0.tgz#07fe7c42e77c9a6379daa6939de55fef99c06ce4"
|
||||
integrity sha512-ygax3PHuPxi58D3/crCDENSFagvG5EsWjXj6AQRUisExPAAiD02RbZaRqTZluFvDbzMJ/29YJdcdIqnllzGVsw==
|
||||
"@serialport/parser-inter-byte-timeout@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.0.1.tgz#bbae1835a0ad0ea6e79dcb27f159231b7051a73a"
|
||||
integrity sha512-awX0bekMZkjb+kjBHsnizAXNfc/grxIqEKdy9Etc6KhgSmratRnjGa7J0rPFP4bTzYWp5sOqlI0ALwBnWCXedA==
|
||||
|
||||
"@serialport/parser-readline@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-readline/-/parser-readline-10.0.0.tgz#a87b0d92864e917e07010477057b66efe92d3af9"
|
||||
integrity sha512-NI3oRY1+fLg94CZm887rgj4V6KyxaJmbmoRgua9bqRv7v/o0SqN9lSQwdYLHQVHpf03zTX9ziuoCV8w5CI3DQQ==
|
||||
"@serialport/parser-readline@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-readline/-/parser-readline-10.0.1.tgz#c4f7f047d4779c908cfb66d14c0ca0abd7e11f25"
|
||||
integrity sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==
|
||||
dependencies:
|
||||
"@serialport/parser-delimiter" "10.0.0"
|
||||
"@serialport/parser-delimiter" "10.0.1"
|
||||
|
||||
"@serialport/parser-ready@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-ready/-/parser-ready-10.0.0.tgz#eae67e9c0806f5b2d9750c1ef910a3cedb549119"
|
||||
integrity sha512-pmMjRVy0wwVSzRt27AtMV/FJdSL6CdKvLUNx+ziDL9Lt30n85ZzrSdYJOwOB63HVIMg8+JRgiyxKNLs+JgMK2g==
|
||||
"@serialport/parser-ready@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-ready/-/parser-ready-10.0.1.tgz#497c4ae0bcc1866b488d8c4f9d6b4e98c4f08aa3"
|
||||
integrity sha512-4hVDrKNJBd0wcCfa1qQAk+MM6mVWc9oIbUPEKJkWdBrrWOqYacx2UpvQWd+3YGJ04hFqEv1feOSaH3/1hUifEg==
|
||||
|
||||
"@serialport/parser-regex@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-regex/-/parser-regex-10.0.0.tgz#a5dd648baf0952253a995bf0174ba2c19649e279"
|
||||
integrity sha512-kLvK6bbDtpnVMagCK95m08W+XaXXzJpDvjzrMNSAtpaL+yeBu4XlEhHpt9+9S/MOetbz4vgWdZx2buYyw9iiGQ==
|
||||
"@serialport/parser-regex@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/parser-regex/-/parser-regex-10.0.1.tgz#bcb302dda0a9d07ce9b3e554e3d2a41abf3fb5c5"
|
||||
integrity sha512-l8ECuUsan33x5pirQZodlmw0q70Jcxy+oHnXJaqchBTRCbtXlE7+PMFJnmNoIHGqDwt0XALbwpvKcnNBrgvT1g==
|
||||
|
||||
"@serialport/stream@10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/stream/-/stream-10.0.0.tgz#482580b9bdad468ac2a60c41c7fb933f7dc7436f"
|
||||
integrity sha512-KFBGWH6BEn4gXUYHEfbywplDX+sVhlL/Yzn/r7hn+qYnwVjhvQo8Vi35CPKESOUKnMKU48sGSUvNaOwU5znw2g==
|
||||
"@serialport/stream@10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/stream/-/stream-10.0.1.tgz#f38c0e076e9e9ba3255e20e161576879f7d9ae18"
|
||||
integrity sha512-WQ5baxC56Jxo9mXgHq3BPxCXKnfOo3PZxpm6CDaKsZbdsdPYChogRsJCzKjAn6QaKIIFv3/5UdAXKmMCxkeVDA==
|
||||
dependencies:
|
||||
debug "^4.3.2"
|
||||
|
||||
@@ -208,6 +208,11 @@ ansi-regex@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz"
|
||||
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
|
||||
|
||||
ansi-regex@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
@@ -3223,12 +3228,12 @@ run-queue@^1.0.0, run-queue@^1.0.3:
|
||||
dependencies:
|
||||
aproba "^1.1.1"
|
||||
|
||||
rxjs@^7.4.0:
|
||||
version "7.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68"
|
||||
integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==
|
||||
rxjs@^7.5.1:
|
||||
version "7.5.1"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.1.tgz#af73df343cbcab37628197f43ea0c8256f54b157"
|
||||
integrity sha512-KExVEeZWxMZnZhUZtsJcFwz8IvPvgu4G2Z2QyqjZQzUGr32KDYuSxrEYO4w3tFFNbfLozcrKUTvTPi+E9ywJkQ==
|
||||
dependencies:
|
||||
tslib "~2.1.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1:
|
||||
version "5.2.1"
|
||||
@@ -3276,21 +3281,21 @@ serialize-error@^5.0.0:
|
||||
dependencies:
|
||||
type-fest "^0.8.0"
|
||||
|
||||
serialport@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/serialport/-/serialport-10.0.0.tgz#75ec9fd25683dc1acb9d8e24433608f641b0b9e0"
|
||||
integrity sha512-fqOs6u4buZLbTpAPYZP4j2qwcOJ5Jxtg0x+llJFuMc4AVwrxx+iQc68/46aHCr+qJ2Wo2N86AZ/DDY5acSOODA==
|
||||
serialport@^10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialport/-/serialport-10.0.1.tgz#2df0ddcedf507180229973fc46e175f123e7c46f"
|
||||
integrity sha512-RNEUs8mtf6m8593b2qRfkDakxbhPR4VQT0iNKEpJu/JfuWVrSYMqAAWnJOQXOWdJV6ib7rcxCHgHFyarGqJVWw==
|
||||
dependencies:
|
||||
"@serialport/binding-mock" "10.0.0"
|
||||
"@serialport/bindings" "10.0.0"
|
||||
"@serialport/parser-byte-length" "10.0.0"
|
||||
"@serialport/parser-cctalk" "10.0.0"
|
||||
"@serialport/parser-delimiter" "10.0.0"
|
||||
"@serialport/parser-inter-byte-timeout" "10.0.0"
|
||||
"@serialport/parser-readline" "10.0.0"
|
||||
"@serialport/parser-ready" "10.0.0"
|
||||
"@serialport/parser-regex" "10.0.0"
|
||||
"@serialport/stream" "10.0.0"
|
||||
"@serialport/binding-mock" "10.0.1"
|
||||
"@serialport/bindings" "10.0.1"
|
||||
"@serialport/parser-byte-length" "10.0.1"
|
||||
"@serialport/parser-cctalk" "10.0.1"
|
||||
"@serialport/parser-delimiter" "10.0.1"
|
||||
"@serialport/parser-inter-byte-timeout" "10.0.1"
|
||||
"@serialport/parser-readline" "10.0.1"
|
||||
"@serialport/parser-ready" "10.0.1"
|
||||
"@serialport/parser-regex" "10.0.1"
|
||||
"@serialport/stream" "10.0.1"
|
||||
debug "^4.3.2"
|
||||
|
||||
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||
@@ -3507,14 +3512,14 @@ string-width@^3.0.0, string-width@^3.1.0:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^5.1.0"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz"
|
||||
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string.prototype.trimend@^1.0.1:
|
||||
version "1.0.3"
|
||||
@@ -3577,6 +3582,13 @@ strip-ansi@^6.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^5.0.0"
|
||||
|
||||
strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-bom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
|
||||
@@ -3704,15 +3716,10 @@ tough-cookie@~2.5.0:
|
||||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tslib@^2.0.0, tslib@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
|
||||
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
||||
|
||||
tslib@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||
tslib@^2.0.0, tslib@^2.1.0, tslib@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
@@ -4022,10 +4029,10 @@ yargs-parser@^15.0.1:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^20.2.2:
|
||||
version "20.2.7"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
|
||||
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
|
||||
yargs-parser@^21.0.0:
|
||||
version "21.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55"
|
||||
integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==
|
||||
|
||||
yargs-parser@^7.0.0:
|
||||
version "7.0.0"
|
||||
@@ -4051,18 +4058,18 @@ yargs@^14.2.3:
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^15.0.1"
|
||||
|
||||
yargs@^17.2.1:
|
||||
version "17.2.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
|
||||
integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
|
||||
yargs@^17.3.1:
|
||||
version "17.3.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9"
|
||||
integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==
|
||||
dependencies:
|
||||
cliui "^7.0.2"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
require-directory "^2.1.1"
|
||||
string-width "^4.2.0"
|
||||
string-width "^4.2.3"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
yargs-parser "^21.0.0"
|
||||
|
||||
yargs@^8.0.2:
|
||||
version "8.0.2"
|
||||
|
@@ -40,7 +40,7 @@ publish:
|
||||
win:
|
||||
icon: "./build/windows/icon.ico"
|
||||
artifactName: tabby-${version}-portable.${ext}
|
||||
rfc3161TimeStampServer: http://sha256timestamp.ws.symantec.com/sha256/timestamp
|
||||
rfc3161TimeStampServer: http://timestamp.comodoca.com/rfc3161
|
||||
nsis:
|
||||
oneClick: false
|
||||
artifactName: tabby-${version}-setup.${ext}
|
||||
|
3
locale/STOP.txt
Normal file
3
locale/STOP.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Do not submit pull requests for translations.
|
||||
|
||||
Translations are managed at https://crowdin.com/project/tabby
|
1507
locale/app.pot
Normal file
1507
locale/app.pot
Normal file
File diff suppressed because it is too large
Load Diff
1510
locale/hr-HR.po
Normal file
1510
locale/hr-HR.po
Normal file
File diff suppressed because it is too large
Load Diff
1510
locale/zh-CN.po
Normal file
1510
locale/zh-CN.po
Normal file
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -7,7 +7,9 @@
|
||||
"@angular/forms": "^12.0.0",
|
||||
"@angular/platform-browser": "^12.0.0",
|
||||
"@angular/platform-browser-dynamic": "^12.0.0",
|
||||
"@fortawesome/fontawesome-free": "^6.0.0-beta.2",
|
||||
"@biesbjerg/ngx-translate-extract": "^7.0.4",
|
||||
"@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
|
||||
"@fortawesome/fontawesome-free": "^6.0.0-beta3",
|
||||
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
|
||||
"@sentry/cli": "^1.71.0",
|
||||
"@sentry/electron": "^2.5.4",
|
||||
@@ -16,21 +18,21 @@
|
||||
"@types/electron-config": "^3.2.2",
|
||||
"@types/electron-debug": "^2.1.0",
|
||||
"@types/fs-extra": "^9.0.12",
|
||||
"@types/js-yaml": "^4.0.4",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/node": "16.0.1",
|
||||
"@types/sortablejs": "^1.10.7",
|
||||
"@types/webpack-env": "^1.16.2",
|
||||
"@types/webpack-env": "^1.16.3",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"apply-loader": "2.0.0",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^0.21.2",
|
||||
"clone-deep": "^4.0.1",
|
||||
"compare-versions": "^4",
|
||||
"core-js": "^3.18.2",
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "^6.5.1",
|
||||
"deep-equal": "2.0.5",
|
||||
"electron": "16.0.5",
|
||||
"electron": "16.0.6",
|
||||
"electron-builder": "^22.14.5",
|
||||
"electron-download": "^4.1.1",
|
||||
"electron-installer-snap": "^5.1.0",
|
||||
@@ -40,29 +42,31 @@
|
||||
"file-loader": "^6.2.0",
|
||||
"graceful-fs": "^4.2.8",
|
||||
"html-loader": "2.1.2",
|
||||
"json-loader": "0.5.7",
|
||||
"json-loader": "^0.5.7",
|
||||
"lru-cache": "^6.0.0",
|
||||
"macos-release": "^3.0.1",
|
||||
"ngx-sortablejs": "^11.1.0",
|
||||
"ngx-toastr": "^14.0.0",
|
||||
"node-abi": "^3.2.0",
|
||||
"node-sass": "^7.0.0",
|
||||
"npmlog": "5.0.1",
|
||||
"node-sass": "^7.0.1",
|
||||
"npmlog": "6.0.0",
|
||||
"npx": "^10.2.2",
|
||||
"patch-package": "^6.4.7",
|
||||
"pug": "^3.0.2",
|
||||
"po-gettext-loader": "^1.0.0",
|
||||
"pug": "3",
|
||||
"pug-cli": "^1.0.0-alpha6",
|
||||
"pug-html-loader": "1.1.5",
|
||||
"pug-lint": "^2.6.0",
|
||||
"pug-loader": "^2.4.0",
|
||||
"pug-static-loader": "2.0.0",
|
||||
"raw-loader": "4.0.2",
|
||||
"sass-loader": "^12.3.0",
|
||||
"sass-loader": "^12.4.0",
|
||||
"shell-quote": "^1.7.3",
|
||||
"shelljs": "0.8.4",
|
||||
"slugify": "^1.6.1",
|
||||
"slugify": "^1.6.5",
|
||||
"sortablejs": "^1.14.0",
|
||||
"source-code-pro": "^2.38.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-loader": "^3.0.1",
|
||||
"source-sans-pro": "3.6.0",
|
||||
"ssh2": "^1.5.0",
|
||||
"style-loader": "^3.2.1",
|
||||
@@ -73,13 +77,14 @@
|
||||
"typescript": "^4.3.5",
|
||||
"utils-decorators": "^1.10.4",
|
||||
"val-loader": "4.0.0",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"yaml-loader": "0.6.0",
|
||||
"zone.js": "^0.11.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"*/pug": "^3",
|
||||
"lzma-native": "^8.0.0",
|
||||
"*/node-abi": "^3.5.0",
|
||||
"**/graceful-fs": "^4.2.4"
|
||||
@@ -93,7 +98,10 @@
|
||||
"prod": "cross-env TABBY_DEV=1 electron app",
|
||||
"docs": "node scripts/build-docs.js",
|
||||
"lint": "eslint --ext ts */src */lib",
|
||||
"postinstall": "patch-package && node ./scripts/install-deps.js"
|
||||
"postinstall": "patch-package && node ./scripts/install-deps.js",
|
||||
"i18n:pull": "crowdin pull --skip-untranslated-strings",
|
||||
"i18n:extract": "node scripts/i18n-extract.js",
|
||||
"i18n:push": "crowdin push"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
28
scripts/i18n-extract.js
Executable file
28
scripts/i18n-extract.js
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env node
|
||||
const sh = require('shelljs')
|
||||
const fs = require('fs/promises')
|
||||
const vars = require('./vars')
|
||||
const log = require('npmlog')
|
||||
|
||||
const tempOutput = 'locale/app.new.pot'
|
||||
const pot = 'locale/app.pot'
|
||||
const tempHtml = 'locale/tmp-html'
|
||||
|
||||
;(async () => {
|
||||
sh.mkdir('-p', tempHtml)
|
||||
for (const plugin of vars.builtinPlugins) {
|
||||
log.info('extract-pug', plugin)
|
||||
|
||||
sh.exec(`yarn pug --doctype html -s --pretty -O '{require: function(){}}' -o ${tempHtml}/${plugin} ${plugin}`)
|
||||
|
||||
log.info('extract-ts', plugin)
|
||||
sh.exec(`node node_modules/.bin/ngx-translate-extract -i ${plugin}/src -m -s -f pot -o ${tempOutput}`)
|
||||
|
||||
}
|
||||
|
||||
log.info('extract-pug')
|
||||
sh.exec(`node node_modules/.bin/ngx-translate-extract -i ${tempHtml} -f pot -s -o ${tempOutput}`)
|
||||
|
||||
sh.rm('-r', tempHtml)
|
||||
await fs.rename(tempOutput, pot)
|
||||
})()
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-community-color-schemes",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"version": "1.0.170-nightly.0",
|
||||
"description": "Community color schemes for Tabby",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
44
tabby-community-color-schemes/schemes/Rose Pine
Normal file
44
tabby-community-color-schemes/schemes/Rose Pine
Normal file
@@ -0,0 +1,44 @@
|
||||
!
|
||||
! Rosé Pine
|
||||
! https://rosepinetheme.com/
|
||||
!
|
||||
*.foreground: #e0def4
|
||||
*.background: #191724
|
||||
*.cursorColor: #555169
|
||||
!
|
||||
! Black
|
||||
*.color0: #26233a
|
||||
*.color8: #6e6a86
|
||||
!
|
||||
! Red
|
||||
*.color1: #eb6f92
|
||||
*.color9: #eb6f92
|
||||
!
|
||||
! Green
|
||||
*.color2: #31748f
|
||||
*.color10: #31748f
|
||||
!
|
||||
! Yellow
|
||||
*.color3: #f6c177
|
||||
*.color11: #f6c177
|
||||
!
|
||||
! Blue
|
||||
*.color4: #9ccfd8
|
||||
*.color12: #9ccfd8
|
||||
|
||||
! Magenta
|
||||
*.color5: #c4a7e7
|
||||
*.color13: #c4a7e7
|
||||
!
|
||||
! Cyan
|
||||
*.color6: #ebbcba
|
||||
*.color14: #ebbcba
|
||||
!
|
||||
! White
|
||||
*.color7: #e0def4
|
||||
*.color15: #e0def4
|
||||
!
|
||||
! Bold, Italic, Underline
|
||||
!*.colorBD:
|
||||
!*.colorIT:
|
||||
!*.colorUL:
|
44
tabby-community-color-schemes/schemes/Rose Pine Dawn
Normal file
44
tabby-community-color-schemes/schemes/Rose Pine Dawn
Normal file
@@ -0,0 +1,44 @@
|
||||
!
|
||||
! Rosé Pine Dawn
|
||||
! https://rosepinetheme.com/
|
||||
!
|
||||
*.foreground: #575279
|
||||
*.background: #faf4ed
|
||||
*.cursorColor: #9893a5
|
||||
!
|
||||
! Black
|
||||
*.color0: #f2e9de
|
||||
*.color8: #6e6a86
|
||||
!
|
||||
! Red
|
||||
*.color1: #b4637a
|
||||
*.color9: #b4637a
|
||||
!
|
||||
! Green
|
||||
*.color2: #286983
|
||||
*.color10: #286983
|
||||
!
|
||||
! Yellow
|
||||
*.color3: #ea9d34
|
||||
*.color11: #ea9d34
|
||||
!
|
||||
! Blue
|
||||
*.color4: #56949f
|
||||
*.color12: #56949f
|
||||
!
|
||||
! Magenta
|
||||
*.color5: #907aa9
|
||||
*.color13: #907aa9
|
||||
!
|
||||
! Cyan
|
||||
*.color6: #d7827e
|
||||
*.color14: #d7827e
|
||||
!
|
||||
! White
|
||||
*.color7: #575279
|
||||
*.color15: #575279
|
||||
!
|
||||
! Bold, Italic, Underline
|
||||
!*.colorBD:
|
||||
!*.colorIT:
|
||||
!*.colorUL:
|
44
tabby-community-color-schemes/schemes/Rose Pine Moon
Normal file
44
tabby-community-color-schemes/schemes/Rose Pine Moon
Normal file
@@ -0,0 +1,44 @@
|
||||
!
|
||||
! Rosé Pine Moon
|
||||
! https://rosepinetheme.com/
|
||||
!
|
||||
*.foreground: #e0def4
|
||||
*.background: #232136
|
||||
*.cursorColor: #59546d
|
||||
!
|
||||
! Black
|
||||
*.color0: #393552
|
||||
*.color8: #817c9c
|
||||
!
|
||||
! Red
|
||||
*.color1: #eb6f92
|
||||
*.color9: #eb6f92
|
||||
!
|
||||
! Green
|
||||
*.color2: #3e8fb0
|
||||
*.color10: #3e8fb0
|
||||
!
|
||||
! Yellow
|
||||
*.color3: #f6c177
|
||||
*.color11: #f6c177
|
||||
!
|
||||
! Blue
|
||||
*.color4: #9ccfd8
|
||||
*.color12: #9ccfd8
|
||||
!
|
||||
! Magenta
|
||||
*.color5: #c4a7e7
|
||||
*.color13: #c4a7e7
|
||||
!
|
||||
! Cyan
|
||||
*.color6: #ea9a97
|
||||
*.color14: #ea9a97
|
||||
!
|
||||
! White
|
||||
*.color7: #e0def4
|
||||
*.color15: #e0def4
|
||||
!
|
||||
! Bold, Italic, Underline
|
||||
!*.colorBD:
|
||||
!*.colorIT:
|
||||
!*.colorUL:
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-core",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"version": "1.0.170-nightly.0",
|
||||
"description": "Tabby core",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
@@ -17,12 +17,15 @@
|
||||
"author": "Eugene Pankov",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@ngx-translate/core": "^14.0.0",
|
||||
"bootstrap": "^4.1.3",
|
||||
"deepmerge": "^4.1.1",
|
||||
"js-yaml": "^4.0.0",
|
||||
"messageformat": "^2.3.0",
|
||||
"mixpanel": "^0.13.0",
|
||||
"ngx-filesize": "^2.0.16",
|
||||
"ngx-perfect-scrollbar": "^10.1.0",
|
||||
"ngx-translate-messageformat-compiler": "^4.11.0",
|
||||
"readable-stream": "3.6.0",
|
||||
"uuid": "^8.0.0"
|
||||
},
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export { BaseComponent, SubscriptionContainer } from '../components/base.component'
|
||||
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
||||
export { BaseTabComponent, BaseTabProcess, GetRecoveryTokenOptions } from '../components/baseTab.component'
|
||||
export { TabHeaderComponent } from '../components/tabHeader.component'
|
||||
export { SplitTabComponent, SplitContainer, SplitDirection, SplitOrientation } from '../components/splitTab.component'
|
||||
export { TabRecoveryProvider, RecoveryToken } from './tabRecovery'
|
||||
@@ -35,4 +35,5 @@ export { TabsService, NewTabParameters, TabComponentType } from '../services/tab
|
||||
export { UpdaterService } from '../services/updater.service'
|
||||
export { VaultService, Vault, VaultSecret, VaultFileSecret, VAULT_SECRET_TYPE_FILE, StoredVault, VaultSecretKey } from '../services/vault.service'
|
||||
export { FileProvidersService } from '../services/fileProviders.service'
|
||||
export { LocaleService, TranslateServiceWrapper as TranslateService } from '../services/locale.service'
|
||||
export * from '../utils'
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import deepClone from 'clone-deep'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { NewTabParameters } from '../services/tabs.service'
|
||||
|
||||
@@ -38,14 +37,4 @@ export abstract class TabRecoveryProvider <T extends BaseTabComponent> {
|
||||
* or `null` if this token is from a different tab type or is not supported
|
||||
*/
|
||||
abstract recover (recoveryToken: RecoveryToken): Promise<NewTabParameters<T>>
|
||||
|
||||
/**
|
||||
* @param recoveryToken a recovery token found in the saved tabs list
|
||||
* @returns [[RecoveryToken]] a new recovery token to create the duplicate tab from
|
||||
*
|
||||
* The default implementation just returns a deep copy of the original token
|
||||
*/
|
||||
duplicate (recoveryToken: RecoveryToken): RecoveryToken {
|
||||
return deepClone(recoveryToken)
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
|
||||
import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider'
|
||||
import { HostAppService, Platform } from './api/hostApp'
|
||||
@@ -12,6 +13,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
||||
constructor (
|
||||
private hostApp: HostAppService,
|
||||
private profilesService: ProfilesService,
|
||||
private translate: TranslateService,
|
||||
hotkeys: HotkeysService,
|
||||
) {
|
||||
super()
|
||||
@@ -35,7 +37,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
||||
icon: this.hostApp.platform === Platform.Web
|
||||
? require('./icons/plus.svg')
|
||||
: require('./icons/profiles.svg'),
|
||||
title: 'Profiles and connections',
|
||||
title: this.translate.instant('Profiles and connections'),
|
||||
click: () => this.activate(),
|
||||
},
|
||||
...this.profilesService.getRecentProfiles().map(profile => ({
|
||||
|
@@ -88,7 +88,7 @@ $side-tab-width: 200px;
|
||||
padding: 0 12px;
|
||||
flex: 0 0 auto;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: 0.25s all;
|
||||
transition: 0.125s all ease-out;
|
||||
font-size: 12px;
|
||||
|
||||
text-transform: uppercase;
|
||||
|
@@ -14,6 +14,7 @@ import { UpdaterService } from '../services/updater.service'
|
||||
import { BaseTabComponent } from './baseTab.component'
|
||||
import { SafeModeModalComponent } from './safeModeModal.component'
|
||||
import { TabBodyComponent } from './tabBody.component'
|
||||
import { SplitTabComponent } from './splitTab.component'
|
||||
import { AppService, FileTransfer, HostWindowService, PlatformService, ToolbarButton, ToolbarButtonProvider } from '../api'
|
||||
|
||||
/** @hidden */
|
||||
@@ -196,6 +197,13 @@ export class AppRootComponent {
|
||||
}
|
||||
|
||||
onTabsReordered (event: CdkDragDrop<BaseTabComponent[]>) {
|
||||
const tab: BaseTabComponent = event.item.data
|
||||
if (!this.app.tabs.includes(tab)) {
|
||||
if (tab.parent instanceof SplitTabComponent) {
|
||||
tab.parent.removeTab(tab)
|
||||
this.app.wrapAndAddTab(tab)
|
||||
}
|
||||
}
|
||||
moveItemInArray(this.app.tabs, event.previousIndex, event.currentIndex)
|
||||
this.app.emitTabsChanged()
|
||||
}
|
||||
|
@@ -11,6 +11,10 @@ export interface BaseTabProcess {
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface GetRecoveryTokenOptions {
|
||||
includeState: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for custom tab components
|
||||
*/
|
||||
@@ -136,7 +140,7 @@ export abstract class BaseTabComponent extends BaseComponent {
|
||||
* @return JSON serializable tab state representation
|
||||
* for your [[TabRecoveryProvider]] to parse
|
||||
*/
|
||||
async getRecoveryToken (): Promise<RecoveryToken|null> {
|
||||
async getRecoveryToken (options?: GetRecoveryTokenOptions): Promise<RecoveryToken|null> { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
return null
|
||||
}
|
||||
|
||||
|
@@ -2,5 +2,5 @@
|
||||
input.form-control(type='text', #input, [(ngModel)]='value', (keyup.enter)='save()', autofocus)
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-primary((click)='save()') Save
|
||||
button.btn.btn-secondary((click)='close()') Cancel
|
||||
button.btn.btn-primary((click)='save()', translate) Save
|
||||
button.btn.btn-secondary((click)='close()', translate) Cancel
|
||||
|
@@ -1,7 +1,7 @@
|
||||
.modal-body
|
||||
.alert.alert-danger Tabby could not start with your plugins, so all third party plugins have been disabled in this session. The error was:
|
||||
.alert.alert-danger(translate) Tabby could not start with your plugins, so all third party plugins have been disabled in this session. The error was:
|
||||
|
||||
pre {{error}}
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-primary((click)='close()') Close
|
||||
button.btn.btn-primary((click)='close()', translate) Close
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, AfterViewInit, OnDestroy } from '@angular/core'
|
||||
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
||||
import { BaseTabComponent, BaseTabProcess, GetRecoveryTokenOptions } from './baseTab.component'
|
||||
import { TabRecoveryProvider, RecoveryToken } from '../api/tabRecovery'
|
||||
import { TabsService, NewTabParameters } from '../services/tabs.service'
|
||||
import { HotkeysService } from '../services/hotkeys.service'
|
||||
@@ -93,13 +93,13 @@ export class SplitContainer {
|
||||
return s
|
||||
}
|
||||
|
||||
async serialize (tabsRecovery: TabRecoveryService): Promise<RecoveryToken> {
|
||||
async serialize (tabsRecovery: TabRecoveryService, options?: GetRecoveryTokenOptions): Promise<RecoveryToken> {
|
||||
const children: any[] = []
|
||||
for (const child of this.children) {
|
||||
if (child instanceof SplitContainer) {
|
||||
children.push(await child.serialize(tabsRecovery))
|
||||
children.push(await child.serialize(tabsRecovery, options))
|
||||
} else {
|
||||
children.push(await tabsRecovery.getFullRecoveryToken(child))
|
||||
children.push(await tabsRecovery.getFullRecoveryToken(child, options))
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -253,6 +253,9 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
})
|
||||
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
||||
|
||||
this.tabAdded$.subscribe(() => this.updateTitle())
|
||||
this.tabRemoved$.subscribe(() => this.updateTitle())
|
||||
|
||||
this.subscribeUntilDestroyed(this.hotkeys.hotkey$, hotkey => {
|
||||
if (!this.hasFocus || !this.focusedTab) {
|
||||
return
|
||||
@@ -305,7 +308,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
/** @hidden */
|
||||
async ngAfterViewInit (): Promise<void> {
|
||||
if (this._recoveredState) {
|
||||
await this.recoverContainer(this.root, this._recoveredState, this._recoveredState.duplicate)
|
||||
await this.recoverContainer(this.root, this._recoveredState)
|
||||
this.updateTitle()
|
||||
this.layout()
|
||||
setTimeout(() => {
|
||||
@@ -571,8 +574,8 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
return this.root.serialize(this.tabRecovery)
|
||||
async getRecoveryToken (options?: GetRecoveryTokenOptions): Promise<any> {
|
||||
return this.root.serialize(this.tabRecovery, options)
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
@@ -792,7 +795,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
})
|
||||
}
|
||||
|
||||
private async recoverContainer (root: SplitContainer, state: any, duplicate = false) {
|
||||
private async recoverContainer (root: SplitContainer, state: any) {
|
||||
const children: (SplitContainer | BaseTabComponent)[] = []
|
||||
root.orientation = state.orientation
|
||||
root.ratios = state.ratios
|
||||
@@ -803,10 +806,10 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
}
|
||||
if (childState.type === 'app:split-tab') {
|
||||
const child = new SplitContainer()
|
||||
await this.recoverContainer(child, childState, duplicate)
|
||||
await this.recoverContainer(child, childState)
|
||||
children.push(child)
|
||||
} else {
|
||||
const recovered = await this.tabRecovery.recoverTab(childState, duplicate)
|
||||
const recovered = await this.tabRecovery.recoverTab(childState)
|
||||
if (recovered) {
|
||||
const tab = this.tabsService.create(recovered)
|
||||
children.push(tab)
|
||||
@@ -837,11 +840,4 @@ export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabCompon
|
||||
inputs: { _recoveredState: recoveryToken },
|
||||
}
|
||||
}
|
||||
|
||||
duplicate (recoveryToken: RecoveryToken): RecoveryToken {
|
||||
return {
|
||||
...recoveryToken,
|
||||
duplicate: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,9 +15,9 @@ footer.d-flex.align-items-center
|
||||
.btn-group.mr-auto
|
||||
button.btn.btn-dark((click)='homeBase.openGitHub()')
|
||||
i.fab.fa-github
|
||||
span GitHub
|
||||
span(translate) GitHub
|
||||
button.btn.btn-dark((click)='homeBase.reportBug()')
|
||||
i.fas.fa-bug
|
||||
span Report a problem
|
||||
span(translate) Report a problem
|
||||
|
||||
.form-control-static.selectable.no-drag Version: {{homeBase.appVersion}}
|
||||
.form-control-static.selectable.no-drag {{ 'Version: {version}'|translate:{ version: this.homeBase.appVersion } }}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.d-flex.align-items-center
|
||||
.dropdown-header File transfers
|
||||
.dropdown-header(translate) File transfers
|
||||
button.btn.btn-link.ml-auto((click)='removeAll(); $event.stopPropagation()') !{require('../icons/times.svg')}
|
||||
.transfer(*ngFor='let transfer of transfers', (click)='showTransfer(transfer)')
|
||||
.icon(*ngIf='isDownload(transfer)') !{require('../icons/download.svg')}
|
||||
@@ -16,4 +16,11 @@
|
||||
.size {{transfer.getSize()|filesize}}
|
||||
.speed(*ngIf='transfer.getSpeed()') {{transfer.getSpeed()|filesize}}/s
|
||||
|
||||
button.btn.btn-link((click)='removeTransfer(transfer); $event.stopPropagation()') !{require('../icons/times.svg')}
|
||||
button.btn.btn-link(
|
||||
*ngIf='!transfer.isComplete()',
|
||||
(click)='removeTransfer(transfer); $event.stopPropagation()'
|
||||
) !{require('../icons/times.svg')}
|
||||
button.btn.btn-link(
|
||||
*ngIf='transfer.isComplete()',
|
||||
(click)='removeTransfer(transfer); $event.stopPropagation()'
|
||||
) !{require('../icons/check.svg')}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { FileDownload, FileTransfer, PlatformService } from '../api/platform'
|
||||
|
||||
/** @hidden */
|
||||
@@ -11,7 +12,10 @@ export class TransfersMenuComponent {
|
||||
@Input() transfers: FileTransfer[]
|
||||
@Output() transfersChange = new EventEmitter<FileTransfer[]>()
|
||||
|
||||
constructor (private platform: PlatformService) { }
|
||||
constructor (
|
||||
private platform: PlatformService,
|
||||
private translate: TranslateService,
|
||||
) { }
|
||||
|
||||
isDownload (transfer: FileTransfer): boolean {
|
||||
return transfer instanceof FileDownload
|
||||
@@ -40,8 +44,11 @@ export class TransfersMenuComponent {
|
||||
if (this.transfers.some(x => !x.isComplete())) {
|
||||
if ((await this.platform.showMessageBox({
|
||||
type: 'warning',
|
||||
message: 'There are active file transfers',
|
||||
buttons: ['Abort all', 'Do not abort'],
|
||||
message: this.translate.instant('There are active file transfers'),
|
||||
buttons: [
|
||||
this.translate.instant('Abort all'),
|
||||
this.translate.instant('Do not abort'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
})).response === 1) {
|
||||
|
@@ -1,13 +1,16 @@
|
||||
.modal-body
|
||||
.d-flex.align-items-center.mb-3
|
||||
h3.m-0 Vault is locked
|
||||
h3.m-0(translate) Vault is locked
|
||||
.ml-auto(ngbDropdown, placement='bottom-right')
|
||||
button.btn.btn-link(ngbDropdownToggle, (click)='$event.stopPropagation()')
|
||||
span(*ngIf='rememberFor') Remember for {{getRememberForDisplay(rememberFor)}}
|
||||
span(*ngIf='!rememberFor') Do not remember
|
||||
span(
|
||||
*ngIf='rememberFor'
|
||||
) {{ 'Remember for {time}'|translate:{time: getRememberForDisplay(rememberFor)} }}
|
||||
span(*ngIf='!rememberFor', translate) Do not remember
|
||||
div(ngbDropdownMenu)
|
||||
button.dropdown-item(
|
||||
(click)='rememberFor = 0',
|
||||
translate
|
||||
) Do not remember
|
||||
button.dropdown-item(
|
||||
*ngFor='let x of rememberOptions',
|
||||
|
@@ -4,21 +4,21 @@
|
||||
h1.tabby-title Tabby
|
||||
sup α
|
||||
|
||||
.text-center.mb-5 Thank you for downloading Tabby!
|
||||
.text-center.mb-5(translate) Thank you for downloading Tabby!
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Enable analytics
|
||||
.description Help track the number of Tabby installs across the world!
|
||||
.title(translate) Enable analytics
|
||||
.description(translate) Help track the number of Tabby installs across the world!
|
||||
toggle([(ngModel)]='config.store.enableAnalytics')
|
||||
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Enable global hotkey (#[strong Ctrl-Space])
|
||||
.description Toggles the Tabby window visibility
|
||||
.title(translate) Enable global hotkey (Ctrl-Space)
|
||||
.description(translate) Toggles the Tabby window visibility
|
||||
toggle([(ngModel)]='enableGlobalHotkey')
|
||||
|
||||
|
||||
.text-center.mt-5
|
||||
button.btn.btn-primary((click)='closeAndDisable()') Close and never show again
|
||||
button.btn.btn-primary((click)='closeAndDisable()', translate) Close and never show again
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { BaseTabComponent } from './baseTab.component'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
import { HostWindowService } from '../api/hostWindow'
|
||||
@@ -16,9 +17,10 @@ export class WelcomeTabComponent extends BaseTabComponent {
|
||||
constructor (
|
||||
private hostWindow: HostWindowService,
|
||||
public config: ConfigService,
|
||||
translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
this.setTitle('Welcome')
|
||||
this.setTitle(translate.instant('Welcome'))
|
||||
}
|
||||
|
||||
async closeAndDisable () {
|
||||
|
@@ -38,3 +38,4 @@ enableExperimentalFeatures: false
|
||||
pluginBlacklist: []
|
||||
hacks:
|
||||
disableGPU: false
|
||||
language: null
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { ProfilesService } from './services/profiles.service'
|
||||
import { HotkeyDescription, HotkeyProvider } from './api/hotkeyProvider'
|
||||
import { PartialProfile, Profile } from './api'
|
||||
@@ -9,188 +10,189 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
||||
hotkeys: HotkeyDescription[] = [
|
||||
{
|
||||
id: 'profile-selector',
|
||||
name: 'Show profile selector',
|
||||
name: this.translate.instant('Show profile selector'),
|
||||
},
|
||||
{
|
||||
id: 'toggle-fullscreen',
|
||||
name: 'Toggle fullscreen mode',
|
||||
name: this.translate.instant('Toggle fullscreen mode'),
|
||||
},
|
||||
{
|
||||
id: 'rename-tab',
|
||||
name: 'Rename Tab',
|
||||
name: this.translate.instant('Rename Tab'),
|
||||
},
|
||||
{
|
||||
id: 'close-tab',
|
||||
name: 'Close tab',
|
||||
name: this.translate.instant('Close tab'),
|
||||
},
|
||||
{
|
||||
id: 'reopen-tab',
|
||||
name: 'Reopen last tab',
|
||||
name: this.translate.instant('Reopen last tab'),
|
||||
},
|
||||
{
|
||||
id: 'toggle-last-tab',
|
||||
name: 'Toggle last tab',
|
||||
name: this.translate.instant('Toggle last tab'),
|
||||
},
|
||||
{
|
||||
id: 'next-tab',
|
||||
name: 'Next tab',
|
||||
name: this.translate.instant('Next tab'),
|
||||
},
|
||||
{
|
||||
id: 'previous-tab',
|
||||
name: 'Previous tab',
|
||||
name: this.translate.instant('Previous tab'),
|
||||
},
|
||||
{
|
||||
id: 'move-tab-left',
|
||||
name: 'Move tab to the left',
|
||||
name: this.translate.instant('Move tab to the left'),
|
||||
},
|
||||
{
|
||||
id: 'move-tab-right',
|
||||
name: 'Move tab to the right',
|
||||
name: this.translate.instant('Move tab to the right'),
|
||||
},
|
||||
{
|
||||
id: 'rearrange-panes',
|
||||
name: 'Show pane labels (for rearranging)',
|
||||
name: this.translate.instant('Show pane labels (for rearranging)'),
|
||||
},
|
||||
{
|
||||
id: 'duplicate-tab',
|
||||
name: 'Duplicate tab',
|
||||
name: this.translate.instant('Duplicate tab'),
|
||||
},
|
||||
{
|
||||
id: 'tab-1',
|
||||
name: 'Tab 1',
|
||||
name: this.translate.instant('Tab 1'),
|
||||
},
|
||||
{
|
||||
id: 'tab-2',
|
||||
name: 'Tab 2',
|
||||
name: this.translate.instant('Tab 2'),
|
||||
},
|
||||
{
|
||||
id: 'tab-3',
|
||||
name: 'Tab 3',
|
||||
name: this.translate.instant('Tab 3'),
|
||||
},
|
||||
{
|
||||
id: 'tab-4',
|
||||
name: 'Tab 4',
|
||||
name: this.translate.instant('Tab 4'),
|
||||
},
|
||||
{
|
||||
id: 'tab-5',
|
||||
name: 'Tab 5',
|
||||
name: this.translate.instant('Tab 5'),
|
||||
},
|
||||
{
|
||||
id: 'tab-6',
|
||||
name: 'Tab 6',
|
||||
name: this.translate.instant('Tab 6'),
|
||||
},
|
||||
{
|
||||
id: 'tab-7',
|
||||
name: 'Tab 7',
|
||||
name: this.translate.instant('Tab 7'),
|
||||
},
|
||||
{
|
||||
id: 'tab-8',
|
||||
name: 'Tab 8',
|
||||
name: this.translate.instant('Tab 8'),
|
||||
},
|
||||
{
|
||||
id: 'tab-9',
|
||||
name: 'Tab 9',
|
||||
name: this.translate.instant('Tab 9'),
|
||||
},
|
||||
{
|
||||
id: 'tab-10',
|
||||
name: 'Tab 10',
|
||||
name: this.translate.instant('Tab 10'),
|
||||
},
|
||||
{
|
||||
id: 'tab-11',
|
||||
name: 'Tab 11',
|
||||
name: this.translate.instant('Tab 11'),
|
||||
},
|
||||
{
|
||||
id: 'tab-12',
|
||||
name: 'Tab 12',
|
||||
name: this.translate.instant('Tab 12'),
|
||||
},
|
||||
{
|
||||
id: 'tab-13',
|
||||
name: 'Tab 13',
|
||||
name: this.translate.instant('Tab 13'),
|
||||
},
|
||||
{
|
||||
id: 'tab-14',
|
||||
name: 'Tab 14',
|
||||
name: this.translate.instant('Tab 14'),
|
||||
},
|
||||
{
|
||||
id: 'tab-15',
|
||||
name: 'Tab 15',
|
||||
name: this.translate.instant('Tab 15'),
|
||||
},
|
||||
{
|
||||
id: 'tab-16',
|
||||
name: 'Tab 16',
|
||||
name: this.translate.instant('Tab 16'),
|
||||
},
|
||||
{
|
||||
id: 'tab-17',
|
||||
name: 'Tab 17',
|
||||
name: this.translate.instant('Tab 17'),
|
||||
},
|
||||
{
|
||||
id: 'tab-18',
|
||||
name: 'Tab 18',
|
||||
name: this.translate.instant('Tab 18'),
|
||||
},
|
||||
{
|
||||
id: 'tab-19',
|
||||
name: 'Tab 19',
|
||||
name: this.translate.instant('Tab 19'),
|
||||
},
|
||||
{
|
||||
id: 'tab-20',
|
||||
name: 'Tab 20',
|
||||
name: this.translate.instant('Tab 20'),
|
||||
},
|
||||
{
|
||||
id: 'split-right',
|
||||
name: 'Split to the right',
|
||||
name: this.translate.instant('Split to the right'),
|
||||
},
|
||||
{
|
||||
id: 'split-bottom',
|
||||
name: 'Split to the bottom',
|
||||
name: this.translate.instant('Split to the bottom'),
|
||||
},
|
||||
{
|
||||
id: 'split-left',
|
||||
name: 'Split to the left',
|
||||
name: this.translate.instant('Split to the left'),
|
||||
},
|
||||
{
|
||||
id: 'split-top',
|
||||
name: 'Split to the top',
|
||||
name: this.translate.instant('Split to the top'),
|
||||
},
|
||||
{
|
||||
id: 'pane-maximize',
|
||||
name: 'Maximize the active pane',
|
||||
name: this.translate.instant('Maximize the active pane'),
|
||||
},
|
||||
{
|
||||
id: 'pane-nav-up',
|
||||
name: 'Focus the pane above',
|
||||
name: this.translate.instant('Focus the pane above'),
|
||||
},
|
||||
{
|
||||
id: 'pane-nav-down',
|
||||
name: 'Focus the pane below',
|
||||
name: this.translate.instant('Focus the pane below'),
|
||||
},
|
||||
{
|
||||
id: 'pane-nav-left',
|
||||
name: 'Focus the pane on the left',
|
||||
name: this.translate.instant('Focus the pane on the left'),
|
||||
},
|
||||
{
|
||||
id: 'pane-nav-right',
|
||||
name: 'Focus the pane on the right',
|
||||
name: this.translate.instant('Focus the pane on the right'),
|
||||
},
|
||||
{
|
||||
id: 'pane-nav-previous',
|
||||
name: 'Focus previous pane',
|
||||
name: this.translate.instant('Focus previous pane'),
|
||||
},
|
||||
{
|
||||
id: 'pane-nav-next',
|
||||
name: 'Focus next pane',
|
||||
name: this.translate.instant('Focus next pane'),
|
||||
},
|
||||
{
|
||||
id: 'switch-profile',
|
||||
name: 'Switch profile in the active pane',
|
||||
name: this.translate.instant('Switch profile in the active pane'),
|
||||
},
|
||||
{
|
||||
id: 'close-pane',
|
||||
name: 'Close focused pane',
|
||||
name: this.translate.instant('Close focused pane'),
|
||||
},
|
||||
]
|
||||
|
||||
constructor (
|
||||
private profilesService: ProfilesService,
|
||||
private translate: TranslateService,
|
||||
) { super() }
|
||||
|
||||
async provide (): Promise<HotkeyDescription[]> {
|
||||
|
1
tabby-core/src/icons/check.svg
Normal file
1
tabby-core/src/icons/check.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="check" class="svg-inline--fa fa-check" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M480.1 128.1l-272 272C204.3 405.7 198.2 408 192 408s-12.28-2.344-16.97-7.031l-144-144c-9.375-9.375-9.375-24.56 0-33.94s24.56-9.375 33.94 0L192 350.1l255-255c9.375-9.375 24.56-9.375 33.94 0S490.3 119.6 480.1 128.1z"></path></svg>
|
After Width: | Height: | Size: 436 B |
@@ -1,4 +1,4 @@
|
||||
import { NgModule, ModuleWithProviders } from '@angular/core'
|
||||
import { NgModule, ModuleWithProviders, LOCALE_ID } from '@angular/core'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
@@ -7,6 +7,8 @@ import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-sc
|
||||
import { NgxFilesizeModule } from 'ngx-filesize'
|
||||
import { SortablejsModule } from 'ngx-sortablejs'
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||
import { TranslateModule, TranslateCompiler, TranslateService } from '@ngx-translate/core'
|
||||
import { TranslateMessageFormatCompiler, MESSAGE_FORMAT_CONFIG } from 'ngx-translate-messageformat-compiler'
|
||||
|
||||
import { AppRootComponent } from './components/appRoot.component'
|
||||
import { CheckboxComponent } from './components/checkbox.component'
|
||||
@@ -40,6 +42,7 @@ import { AppService } from './services/app.service'
|
||||
import { ConfigService } from './services/config.service'
|
||||
import { VaultFileProvider } from './services/vault.service'
|
||||
import { HotkeysService } from './services/hotkeys.service'
|
||||
import { LocaleService, TranslateServiceWrapper } from './services/locale.service'
|
||||
|
||||
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
||||
import { CoreConfigProvider } from './config'
|
||||
@@ -51,6 +54,10 @@ import { SplitLayoutProfilesService } from './profiles'
|
||||
|
||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||
|
||||
export function TranslateMessageFormatCompilerFactory (): TranslateMessageFormatCompiler {
|
||||
return new TranslateMessageFormatCompiler()
|
||||
}
|
||||
|
||||
const PROVIDERS = [
|
||||
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
||||
{ provide: Theme, useClass: StandardTheme, multi: true },
|
||||
@@ -68,6 +75,19 @@ const PROVIDERS = [
|
||||
{ provide: FileProvider, useClass: VaultFileProvider, multi: true },
|
||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||
{ provide: ProfileProvider, useExisting: SplitLayoutProfilesService, multi: true },
|
||||
{
|
||||
provide: LOCALE_ID,
|
||||
deps: [LocaleService],
|
||||
useFactory: locale => locale.getLocale(),
|
||||
},
|
||||
{
|
||||
provide: MESSAGE_FORMAT_CONFIG,
|
||||
useValue: LocaleService.allLocales,
|
||||
},
|
||||
{
|
||||
provide: TranslateService,
|
||||
useClass: TranslateServiceWrapper,
|
||||
},
|
||||
]
|
||||
|
||||
/** @hidden */
|
||||
@@ -81,6 +101,7 @@ const PROVIDERS = [
|
||||
PerfectScrollbarModule,
|
||||
DragDropModule,
|
||||
SortablejsModule.forRoot({ animation: 150 }),
|
||||
TranslateModule,
|
||||
],
|
||||
declarations: [
|
||||
AppRootComponent,
|
||||
@@ -127,6 +148,7 @@ const PROVIDERS = [
|
||||
AlwaysVisibleTypeaheadDirective,
|
||||
SortablejsModule,
|
||||
DragDropModule,
|
||||
TranslateModule,
|
||||
],
|
||||
})
|
||||
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||
@@ -135,6 +157,8 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
|
||||
config: ConfigService,
|
||||
platform: PlatformService,
|
||||
hotkeys: HotkeysService,
|
||||
public locale: LocaleService,
|
||||
private translate: TranslateService,
|
||||
private profilesService: ProfilesService,
|
||||
private selector: SelectorService,
|
||||
) {
|
||||
@@ -182,8 +206,8 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
|
||||
|
||||
if (provider.supportsQuickConnect) {
|
||||
options.push({
|
||||
name: 'Quick connect',
|
||||
freeInputPattern: 'Connect to "%s"...',
|
||||
name: this.translate.instant('Quick connect'),
|
||||
freeInputPattern: this.translate.instant('Connect to "%s"...'),
|
||||
icon: 'fas fa-arrow-right',
|
||||
callback: query => {
|
||||
const p = provider.quickConnect(query)
|
||||
@@ -194,13 +218,23 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
|
||||
})
|
||||
}
|
||||
|
||||
await this.selector.show('Select profile', options)
|
||||
await this.selector.show(this.translate.instant('Select profile'), options)
|
||||
}
|
||||
|
||||
static forRoot (): ModuleWithProviders<AppModule> {
|
||||
const translateModule = TranslateModule.forRoot({
|
||||
defaultLanguage: 'en',
|
||||
compiler: {
|
||||
provide: TranslateCompiler,
|
||||
useFactory: TranslateMessageFormatCompilerFactory,
|
||||
},
|
||||
})
|
||||
return {
|
||||
ngModule: AppModule,
|
||||
providers: PROVIDERS,
|
||||
providers: [
|
||||
...PROVIDERS,
|
||||
...translateModule.providers!.filter(x => x !== TranslateService),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import slugify from 'slugify'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { ConfigService, NewTabParameters, PartialProfile, Profile, ProfileProvider } from './api'
|
||||
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
||||
|
||||
@@ -15,7 +16,7 @@ export interface SplitLayoutProfile extends Profile {
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfile> {
|
||||
id = 'split-layout'
|
||||
name = 'Saved layout'
|
||||
name = this.translate.instant('Saved layout')
|
||||
configDefaults = {
|
||||
options: {
|
||||
recoveryToken: null,
|
||||
@@ -25,6 +26,7 @@ export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfi
|
||||
constructor (
|
||||
private splitTabRecoveryProvider: SplitTabRecoveryProvider,
|
||||
private config: ConfigService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -42,7 +44,7 @@ export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfi
|
||||
}
|
||||
|
||||
async createProfile (tab: SplitTabComponent, name: string): Promise<void> {
|
||||
const token = await tab.getRecoveryToken()
|
||||
const token = await tab.getRecoveryToken({ includeState: false })
|
||||
const profile: PartialProfile<SplitLayoutProfile> = {
|
||||
id: `${this.id}:custom:${slugify(name)}:${uuidv4()}`,
|
||||
type: this.id,
|
||||
|
@@ -170,11 +170,19 @@ export class AppService {
|
||||
if (params.type as any === SplitTabComponent) {
|
||||
return this.openNewTabRaw(params)
|
||||
}
|
||||
const splitTab = this.tabsService.create({ type: SplitTabComponent })
|
||||
const tab = this.tabsService.create(params)
|
||||
this.wrapAndAddTab(tab)
|
||||
return tab
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an existing tab while wrapping it in a SplitTabComponent
|
||||
*/
|
||||
wrapAndAddTab (tab: BaseTabComponent): SplitTabComponent {
|
||||
const splitTab = this.tabsService.create({ type: SplitTabComponent })
|
||||
splitTab.addTab(tab, null, 'r')
|
||||
this.addTabRaw(splitTab)
|
||||
return tab
|
||||
return splitTab
|
||||
}
|
||||
|
||||
async reopenLastTab (): Promise<BaseTabComponent|null> {
|
||||
@@ -310,9 +318,10 @@ export class AppService {
|
||||
if (checkCanClose && !await tab.canClose()) {
|
||||
return
|
||||
}
|
||||
const token = await this.tabRecovery.getFullRecoveryToken(tab)
|
||||
const token = await this.tabRecovery.getFullRecoveryToken(tab, { includeState: true })
|
||||
if (token) {
|
||||
this.closedTabsStack.push(token)
|
||||
this.closedTabsStack = this.closedTabsStack.slice(-5)
|
||||
}
|
||||
tab.destroy()
|
||||
}
|
||||
|
@@ -2,8 +2,9 @@ import deepClone from 'clone-deep'
|
||||
import deepEqual from 'deep-equal'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as yaml from 'js-yaml'
|
||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||
import { Observable, Subject, AsyncSubject, lastValueFrom } from 'rxjs'
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { ConfigProvider } from '../api/configProvider'
|
||||
import { PlatformService } from '../api/platform'
|
||||
import { HostAppService } from '../api/hostApp'
|
||||
@@ -136,6 +137,7 @@ export class ConfigService {
|
||||
private hostApp: HostAppService,
|
||||
private platform: PlatformService,
|
||||
private vault: VaultService,
|
||||
private translate: TranslateService,
|
||||
@Inject(ConfigProvider) private configProviders: ConfigProvider[],
|
||||
) {
|
||||
this.defaults = this.mergeDefaults()
|
||||
@@ -194,7 +196,7 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
async save (): Promise<void> {
|
||||
await this.ready$
|
||||
await lastValueFrom(this.ready$)
|
||||
if (!this._store) {
|
||||
throw new Error('Cannot save an empty store')
|
||||
}
|
||||
@@ -268,8 +270,8 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
private emitChange (): void {
|
||||
this.changed.next()
|
||||
this.vault.setStore(this.store.vault)
|
||||
this.changed.next()
|
||||
}
|
||||
|
||||
private migrate (config) {
|
||||
@@ -360,9 +362,13 @@ export class ConfigService {
|
||||
} catch (e) {
|
||||
let result = await this.platform.showMessageBox({
|
||||
type: 'error',
|
||||
message: 'Could not decrypt config',
|
||||
message: this.translate.instant('Could not decrypt config'),
|
||||
detail: e.toString(),
|
||||
buttons: ['Try again', 'Erase config', 'Quit'],
|
||||
buttons: [
|
||||
this.translate.instant('Try again'),
|
||||
this.translate.instant('Erase config'),
|
||||
this.translate.instant('Quit'),
|
||||
],
|
||||
defaultId: 0,
|
||||
})
|
||||
if (result.response === 2) {
|
||||
@@ -371,9 +377,12 @@ export class ConfigService {
|
||||
if (result.response === 1) {
|
||||
result = await this.platform.showMessageBox({
|
||||
type: 'warning',
|
||||
message: 'Are you sure?',
|
||||
message: this.translate.instant('Are you sure?'),
|
||||
detail: e.toString(),
|
||||
buttons: ['Erase config', 'Quit'],
|
||||
buttons: [
|
||||
this.translate.instant('Erase config'),
|
||||
this.translate.instant('Quit'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
})
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { FileProvider, NotificationsService, SelectorService } from '../api'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -7,6 +8,7 @@ export class FileProvidersService {
|
||||
private constructor (
|
||||
private selector: SelectorService,
|
||||
private notifications: NotificationsService,
|
||||
private translate: TranslateService,
|
||||
@Inject(FileProvider) private fileProviders: FileProvider[],
|
||||
) { }
|
||||
|
||||
@@ -34,15 +36,18 @@ export class FileProvidersService {
|
||||
}
|
||||
}))
|
||||
if (!providers.length) {
|
||||
this.notifications.error('Vault master passphrase needs to be set to allow storing secrets')
|
||||
this.notifications.error(this.translate.instant('Vault master passphrase needs to be set to allow storing secrets'))
|
||||
throw new Error('No available file providers')
|
||||
}
|
||||
if (providers.length === 1) {
|
||||
return providers[0]
|
||||
}
|
||||
return this.selector.show('Select file storage', providers.map(p => ({
|
||||
name: p.name,
|
||||
result: p,
|
||||
})))
|
||||
return this.selector.show(
|
||||
this.translate.instant('Select file storage'),
|
||||
providers.map(p => ({
|
||||
name: p.name,
|
||||
result: p,
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
107
tabby-core/src/services/locale.service.ts
Normal file
107
tabby-core/src/services/locale.service.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { registerLocaleData } from '@angular/common'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
|
||||
import localeEN from '@angular/common/locales/en-GB'
|
||||
import localeHR from '@angular/common/locales/hr'
|
||||
import localeZH from '@angular/common/locales/zh'
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { distinctUntilChanged } from 'rxjs/operators'
|
||||
import { ConfigService } from './config.service'
|
||||
import { LogService, Logger } from './log.service'
|
||||
|
||||
registerLocaleData(localeEN)
|
||||
registerLocaleData(localeHR)
|
||||
registerLocaleData(localeZH)
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TranslateServiceWrapper extends TranslateService {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
getParsedResult (translations: any, key: any, interpolateParams?: any): any {
|
||||
this.translations[this.defaultLang][key] ??= this.compiler.compile(key, this.defaultLang)
|
||||
return super.getParsedResult(translations, key, interpolateParams)
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LocaleService {
|
||||
private logger: Logger
|
||||
|
||||
static readonly allLocales = ['en-US', 'hr-HR', 'zh-CN']
|
||||
readonly allLanguages: { code: string, name: string }[]
|
||||
|
||||
get localeChanged$ (): Observable<string> {
|
||||
return this.localeChanged.pipe(distinctUntilChanged())
|
||||
}
|
||||
|
||||
private locale = 'en-US'
|
||||
private localeChanged = new Subject<string>()
|
||||
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
private translate: TranslateService,
|
||||
log: LogService,
|
||||
) {
|
||||
this.logger = log.create('translate')
|
||||
config.changed$.subscribe(() => {
|
||||
this.refresh()
|
||||
})
|
||||
config.ready$.subscribe(() => {
|
||||
this.refresh()
|
||||
})
|
||||
|
||||
this.allLanguages = [
|
||||
{
|
||||
code: 'en-US',
|
||||
name: translate.instant('English'),
|
||||
},
|
||||
{
|
||||
code: 'hr-HR',
|
||||
name: translate.instant('Croatian'),
|
||||
},
|
||||
{
|
||||
code: 'zh-CN',
|
||||
name: translate.instant('Chinese (simplified)'),
|
||||
},
|
||||
]
|
||||
|
||||
this.translate.setTranslation('en-US', {})
|
||||
}
|
||||
|
||||
refresh (): void {
|
||||
let lang = this.config.store.language
|
||||
if (!lang) {
|
||||
for (const systemLanguage of navigator.languages) {
|
||||
if (!lang && this.allLanguages.some(x => x.code === systemLanguage)) {
|
||||
lang = systemLanguage
|
||||
}
|
||||
}
|
||||
}
|
||||
lang ??= 'en-US'
|
||||
this.setLocale(lang)
|
||||
}
|
||||
|
||||
async setLocale (lang: string): Promise<void> {
|
||||
if (!this.translate.langs.includes(lang) && lang !== 'en-US') {
|
||||
this.translate.addLangs([lang])
|
||||
|
||||
const po = require(`../../../locale/${lang}.po`).translations['']
|
||||
const translation = {}
|
||||
for (const k of Object.keys(po)) {
|
||||
translation[k] = po[k].msgstr[0] || k
|
||||
}
|
||||
|
||||
this.translate.setTranslation(lang, translation)
|
||||
}
|
||||
|
||||
this.translate.setDefaultLang(lang)
|
||||
|
||||
this.locale = lang
|
||||
this.localeChanged.next(lang)
|
||||
this.logger.debug('Setting language to', lang)
|
||||
}
|
||||
|
||||
getLocale (): string {
|
||||
return this.locale
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { NewTabParameters } from './tabs.service'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider'
|
||||
@@ -29,6 +30,7 @@ export class ProfilesService {
|
||||
private config: ConfigService,
|
||||
private notifications: NotificationsService,
|
||||
private selector: SelectorService,
|
||||
private translate: TranslateService,
|
||||
@Inject(ProfileProvider) private profileProviders: ProfileProvider<Profile>[],
|
||||
) { }
|
||||
|
||||
@@ -103,7 +105,7 @@ export class ProfilesService {
|
||||
|
||||
let options: SelectorOption<void>[] = recentProfiles.map(p => ({
|
||||
...this.selectorOptionForProfile(p),
|
||||
group: 'Recent',
|
||||
group: this.translate.instant('Recent'),
|
||||
icon: 'fas fa-history',
|
||||
color: p.color,
|
||||
callback: async () => {
|
||||
@@ -115,8 +117,8 @@ export class ProfilesService {
|
||||
}))
|
||||
if (recentProfiles.length) {
|
||||
options.push({
|
||||
name: 'Clear recent profiles',
|
||||
group: 'Recent',
|
||||
name: this.translate.instant('Clear recent profiles'),
|
||||
group: this.translate.instant('Recent'),
|
||||
icon: 'fas fa-eraser',
|
||||
callback: async () => {
|
||||
window.localStorage.removeItem('recentProfiles')
|
||||
@@ -142,7 +144,7 @@ export class ProfilesService {
|
||||
try {
|
||||
const { SettingsTabComponent } = window['nodeRequire']('tabby-settings')
|
||||
options.push({
|
||||
name: 'Manage profiles',
|
||||
name: this.translate.instant('Manage profiles'),
|
||||
icon: 'fas fa-window-restore',
|
||||
callback: () => {
|
||||
this.app.openNewTabRaw({
|
||||
@@ -156,8 +158,8 @@ export class ProfilesService {
|
||||
|
||||
if (this.getProviders().some(x => x.supportsQuickConnect)) {
|
||||
options.push({
|
||||
name: 'Quick connect',
|
||||
freeInputPattern: 'Connect to "%s"...',
|
||||
name: this.translate.instant('Quick connect'),
|
||||
freeInputPattern: this.translate.instant('Connect to "%s"...'),
|
||||
icon: 'fas fa-arrow-right',
|
||||
callback: query => {
|
||||
const profile = this.quickConnect(query)
|
||||
@@ -165,7 +167,7 @@ export class ProfilesService {
|
||||
},
|
||||
})
|
||||
}
|
||||
await this.selector.show('Select profile or enter an address', options)
|
||||
await this.selector.show(this.translate.instant('Select profile or enter an address'), options)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { TabRecoveryProvider, RecoveryToken } from '../api/tabRecovery'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { BaseTabComponent, GetRecoveryTokenOptions } from '../components/baseTab.component'
|
||||
import { Logger, LogService } from './log.service'
|
||||
import { ConfigService } from './config.service'
|
||||
import { NewTabParameters } from './tabs.service'
|
||||
@@ -25,13 +25,13 @@ export class TabRecoveryService {
|
||||
}
|
||||
window.localStorage.tabsRecovery = JSON.stringify(
|
||||
(await Promise.all(
|
||||
tabs.map(async tab => this.getFullRecoveryToken(tab))
|
||||
tabs.map(async tab => this.getFullRecoveryToken(tab, { includeState: true }))
|
||||
)).filter(token => !!token)
|
||||
)
|
||||
}
|
||||
|
||||
async getFullRecoveryToken (tab: BaseTabComponent): Promise<RecoveryToken|null> {
|
||||
const token = await tab.getRecoveryToken()
|
||||
async getFullRecoveryToken (tab: BaseTabComponent, options?: GetRecoveryTokenOptions): Promise<RecoveryToken|null> {
|
||||
const token = await tab.getRecoveryToken(options)
|
||||
if (token) {
|
||||
token.tabTitle = tab.title
|
||||
token.tabCustomTitle = tab.customTitle
|
||||
@@ -43,15 +43,12 @@ export class TabRecoveryService {
|
||||
return token
|
||||
}
|
||||
|
||||
async recoverTab (token: RecoveryToken, duplicate = false): Promise<NewTabParameters<BaseTabComponent>|null> {
|
||||
async recoverTab (token: RecoveryToken): Promise<NewTabParameters<BaseTabComponent>|null> {
|
||||
for (const provider of this.config.enabledServices(this.tabRecoveryProviders ?? [])) {
|
||||
try {
|
||||
if (!await provider.applicableTo(token)) {
|
||||
continue
|
||||
}
|
||||
if (duplicate) {
|
||||
token = provider.duplicate(token)
|
||||
}
|
||||
const tab = await provider.recover(token)
|
||||
tab.inputs = tab.inputs ?? {}
|
||||
tab.inputs.color = token.tabColor ?? null
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import deepClone from 'clone-deep'
|
||||
import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { TabRecoveryService } from './tabRecovery.service'
|
||||
@@ -48,7 +49,7 @@ export class TabsService {
|
||||
if (!token) {
|
||||
return null
|
||||
}
|
||||
const dup = await this.tabRecovery.recoverTab(token, true)
|
||||
const dup = await this.tabRecovery.recoverTab(deepClone(token))
|
||||
if (dup) {
|
||||
return this.create(dup)
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Injectable } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { AppService } from './services/app.service'
|
||||
import { BaseTabComponent } from './components/baseTab.component'
|
||||
@@ -22,6 +23,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -29,7 +31,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
|
||||
let items: MenuItemOptions[] = [
|
||||
{
|
||||
label: 'Close',
|
||||
label: this.translate.instant('Close'),
|
||||
click: () => {
|
||||
if (this.app.tabs.includes(tab)) {
|
||||
this.app.closeTab(tab, true)
|
||||
@@ -43,7 +45,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||
items = [
|
||||
...items,
|
||||
{
|
||||
label: 'Close other tabs',
|
||||
label: this.translate.instant('Close other tabs'),
|
||||
click: () => {
|
||||
for (const t of this.app.tabs.filter(x => x !== tab)) {
|
||||
this.app.closeTab(t, true)
|
||||
@@ -51,7 +53,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Close tabs to the right',
|
||||
label: this.translate.instant('Close tabs to the right'),
|
||||
click: () => {
|
||||
for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) {
|
||||
this.app.closeTab(t, true)
|
||||
@@ -59,7 +61,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Close tabs to the left',
|
||||
label: this.translate.instant('Close tabs to the left'),
|
||||
click: () => {
|
||||
for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) {
|
||||
this.app.closeTab(t, true)
|
||||
@@ -71,13 +73,13 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||
if (tab.parent instanceof SplitTabComponent) {
|
||||
const directions: SplitDirection[] = ['r', 'b', 'l', 't']
|
||||
items.push({
|
||||
label: 'Split',
|
||||
label: this.translate.instant('Split'),
|
||||
submenu: directions.map(dir => ({
|
||||
label: {
|
||||
r: 'Right',
|
||||
b: 'Down',
|
||||
l: 'Left',
|
||||
t: 'Up',
|
||||
r: this.translate.instant('Right'),
|
||||
b: this.translate.instant('Down'),
|
||||
l: this.translate.instant('Left'),
|
||||
t: this.translate.instant('Up'),
|
||||
}[dir],
|
||||
click: () => {
|
||||
(tab.parent as SplitTabComponent).splitTab(tab, dir)
|
||||
@@ -99,6 +101,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
private app: AppService,
|
||||
private ngbModal: NgbModal,
|
||||
private splitLayoutProfilesService: SplitLayoutProfilesService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -109,18 +112,18 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
items = [
|
||||
...items,
|
||||
{
|
||||
label: 'Rename',
|
||||
label: this.translate.instant('Rename'),
|
||||
click: () => tabHeader.showRenameTabModal(),
|
||||
},
|
||||
{
|
||||
label: 'Duplicate',
|
||||
label: this.translate.instant('Duplicate'),
|
||||
click: () => this.app.duplicateTab(tab),
|
||||
},
|
||||
{
|
||||
label: 'Color',
|
||||
label: this.translate.instant('Color'),
|
||||
sublabel: TAB_COLORS.find(x => x.value === tab.color)?.name,
|
||||
submenu: TAB_COLORS.map(color => ({
|
||||
label: color.name,
|
||||
label: this.translate.instant(color.name),
|
||||
type: 'radio',
|
||||
checked: tab.color === color.value,
|
||||
click: () => {
|
||||
@@ -132,10 +135,10 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
if (tab instanceof SplitTabComponent && tab.getAllTabs().length > 1) {
|
||||
items.push({
|
||||
label: 'Save layout as profile',
|
||||
label: this.translate.instant('Save layout as profile'),
|
||||
click: async () => {
|
||||
const modal = this.ngbModal.open(PromptModalComponent)
|
||||
modal.componentInstance.prompt = 'Profile name'
|
||||
modal.componentInstance.prompt = this.translate.instant('Profile name')
|
||||
const name = (await modal.result)?.value
|
||||
if (!name) {
|
||||
return
|
||||
@@ -154,6 +157,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -167,10 +171,10 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
||||
if (process) {
|
||||
items.push({
|
||||
enabled: false,
|
||||
label: 'Current process: ' + process.name,
|
||||
label: this.translate.instant('Current process: {name}', process),
|
||||
})
|
||||
items.push({
|
||||
label: 'Notify when done',
|
||||
label: this.translate.instant('Notify when done'),
|
||||
type: 'checkbox',
|
||||
checked: extTab.__completionNotificationEnabled,
|
||||
click: () => {
|
||||
@@ -178,7 +182,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
if (extTab.__completionNotificationEnabled) {
|
||||
this.app.observeTabCompletion(tab).subscribe(() => {
|
||||
new Notification('Process completed', {
|
||||
new Notification(this.translate.instant('Process completed'), {
|
||||
body: process.name,
|
||||
}).addEventListener('click', () => {
|
||||
this.app.selectTab(tab)
|
||||
@@ -192,7 +196,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
||||
})
|
||||
}
|
||||
items.push({
|
||||
label: 'Notify on activity',
|
||||
label: this.translate.instant('Notify on activity'),
|
||||
type: 'checkbox',
|
||||
checked: !!extTab.__outputNotificationSubscription,
|
||||
click: () => {
|
||||
@@ -204,7 +208,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
||||
if (extTab.__outputNotificationSubscription && active) {
|
||||
extTab.__outputNotificationSubscription.unsubscribe()
|
||||
extTab.__outputNotificationSubscription = null
|
||||
new Notification('Tab activity', {
|
||||
new Notification(this.translate.instant('Tab activity'), {
|
||||
body: tab.title,
|
||||
}).addEventListener('click', () => {
|
||||
this.app.selectTab(tab)
|
||||
@@ -228,6 +232,7 @@ export class ProfilesContextMenu extends TabContextMenuItemProvider {
|
||||
private profilesService: ProfilesService,
|
||||
private tabsService: TabsService,
|
||||
private app: AppService,
|
||||
private translate: TranslateService,
|
||||
hotkeys: HotkeysService,
|
||||
) {
|
||||
super()
|
||||
@@ -270,7 +275,7 @@ export class ProfilesContextMenu extends TabContextMenuItemProvider {
|
||||
if (!tabHeader && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
|
||||
return [
|
||||
{
|
||||
label: 'Switch profile',
|
||||
label: this.translate.instant('Switch profile'),
|
||||
click: () => this.switchTabProfile(tab),
|
||||
},
|
||||
]
|
||||
|
@@ -386,3 +386,7 @@ start-page footer {
|
||||
background: #ffffff4a !important;
|
||||
border-bottom: 1px solid #00000026 !important;
|
||||
}
|
||||
|
||||
.bg-dark{
|
||||
background-color: $base2 !important;
|
||||
}
|
@@ -124,8 +124,13 @@ app-root {
|
||||
|
||||
&.platform-win32, &.platform-linux {
|
||||
border: 1px solid #111;
|
||||
&>.content .tab-bar .tabs tab-header:first-child {
|
||||
border-left: none;
|
||||
|
||||
&>.content {
|
||||
margin: -1px; // expand the content into the border
|
||||
|
||||
.tab-bar .tabs tab-header:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,28 +1,41 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TranslateService } from '@ngx-translate/core'
|
||||
import { Theme } from './api'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class StandardTheme extends Theme {
|
||||
name = 'Standard'
|
||||
name = this.translate.instant('Standard')
|
||||
css = require('./theme.scss')
|
||||
terminalBackground = '#222a33'
|
||||
|
||||
constructor (private translate: TranslateService) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class StandardCompactTheme extends Theme {
|
||||
name = 'Compact'
|
||||
name = this.translate.instant('Compact')
|
||||
css = require('./theme.compact.scss')
|
||||
terminalBackground = '#222a33'
|
||||
macOSWindowButtonsInsetX = 8
|
||||
macOSWindowButtonsInsetY = 6
|
||||
|
||||
constructor (private translate: TranslateService) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class PaperTheme extends Theme {
|
||||
name = 'Paper'
|
||||
name = this.translate.instant('Paper')
|
||||
css = require('./theme.paper.scss')
|
||||
terminalBackground = '#f7f1e0'
|
||||
|
||||
constructor (private translate: TranslateService) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import * as os from 'os'
|
||||
import { NgZone } from '@angular/core'
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
|
||||
|
||||
export const WIN_BUILD_CONPTY_SUPPORTED = 17692
|
||||
export const WIN_BUILD_CONPTY_STABLE = 18309
|
||||
@@ -56,13 +57,13 @@ export class ResettableTimeout {
|
||||
}
|
||||
|
||||
export const TAB_COLORS = [
|
||||
{ name: 'No color', value: null },
|
||||
{ name: 'Blue', value: '#0275d8' },
|
||||
{ name: 'Green', value: '#5cb85c' },
|
||||
{ name: 'Orange', value: '#f0ad4e' },
|
||||
{ name: 'Purple', value: '#613d7c' },
|
||||
{ name: 'Red', value: '#d9534f' },
|
||||
{ name: 'Yellow', value: '#ffd500' },
|
||||
{ name: _('No color'), value: null },
|
||||
{ name: _('Blue'), value: '#0275d8' },
|
||||
{ name: _('Green'), value: '#5cb85c' },
|
||||
{ name: _('Orange'), value: '#f0ad4e' },
|
||||
{ name: _('Purple'), value: '#613d7c' },
|
||||
{ name: _('Red'), value: '#d9534f' },
|
||||
{ name: _('Yellow'), value: '#ffd500' },
|
||||
]
|
||||
|
||||
export function serializeFunction <T extends () => Promise<any>> (fn: T): T {
|
||||
|
@@ -2,6 +2,13 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@ngx-translate/core@^14.0.0":
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-14.0.0.tgz#af421d0e1a28376843f0fed375cd2fae7630a5ff"
|
||||
integrity sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
agent-base@6:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
@@ -56,6 +63,37 @@ js-yaml@^4.0.0:
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
make-plural@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-4.3.0.tgz#f23de08efdb0cac2e0c9ba9f315b0dff6b4c2735"
|
||||
integrity sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==
|
||||
optionalDependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
messageformat-formatters@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz#0492c1402a48775f751c9b17c0354e92be012b08"
|
||||
integrity sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==
|
||||
|
||||
messageformat-parser@^4.1.2:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-4.1.3.tgz#b824787f57fcda7d50769f5b63e8d4fda68f5b9e"
|
||||
integrity sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==
|
||||
|
||||
messageformat@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/messageformat/-/messageformat-2.3.0.tgz#de263c49029d5eae65d7ee25e0754f57f425ad91"
|
||||
integrity sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==
|
||||
dependencies:
|
||||
make-plural "^4.3.0"
|
||||
messageformat-formatters "^2.0.1"
|
||||
messageformat-parser "^4.1.2"
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
mixpanel@^0.13.0:
|
||||
version "0.13.0"
|
||||
resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.13.0.tgz#699bf510d9ba013c75edcf979ff1e24085fde9d2"
|
||||
@@ -85,6 +123,13 @@ ngx-perfect-scrollbar@^10.1.0:
|
||||
resize-observer-polyfill "^1.5.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
ngx-translate-messageformat-compiler@^4.11.0:
|
||||
version "4.11.0"
|
||||
resolved "https://registry.yarnpkg.com/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.11.0.tgz#c9b71dd139ba5fcdcd809001e22622de589fd707"
|
||||
integrity sha512-OdGfWV4fF3DhZqGIHcLmOnQDufugmZ+E90NYr1UPGRZgT10lilr9oLmIrisy3lW4THnZFNo9JXsX7+fX84LbDw==
|
||||
dependencies:
|
||||
tslib "^1.10.0"
|
||||
|
||||
perfect-scrollbar@1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz#821d224ed8ff61990c23f26db63048cdc75b6b83"
|
||||
@@ -116,11 +161,21 @@ string_decoder@^1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
tslib@^1.10.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||
|
||||
tslib@^2.3.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-electron",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"version": "1.0.170-nightly.0",
|
||||
"description": "Electron-specific bindings",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
|
||||
import { HotkeyDescription, HotkeyProvider, TranslateService } from 'tabby-core'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@@ -7,14 +7,16 @@ export class ElectronHotkeyProvider extends HotkeyProvider {
|
||||
hotkeys: HotkeyDescription[] = [
|
||||
{
|
||||
id: 'new-window',
|
||||
name: 'New window',
|
||||
name: this.translate.instant('New window'),
|
||||
},
|
||||
{
|
||||
id: 'toggle-window',
|
||||
name: 'Toggle terminal window',
|
||||
name: this.translate.instant('Toggle terminal window'),
|
||||
},
|
||||
]
|
||||
|
||||
constructor (private translate: TranslateService) { super() }
|
||||
|
||||
async provide (): Promise<HotkeyDescription[]> {
|
||||
return this.hotkeys
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import { promisify } from 'util'
|
||||
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
|
||||
import { execFile } from 'mz/child_process'
|
||||
import { Injectable, NgZone } from '@angular/core'
|
||||
import { PlatformService, ClipboardContent, HostAppService, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise } from 'tabby-core'
|
||||
import { PlatformService, ClipboardContent, HostAppService, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core'
|
||||
import { ElectronService } from '../services/electron.service'
|
||||
import { ElectronHostWindow } from './hostWindow.service'
|
||||
import { ShellIntegrationService } from './shellIntegration.service'
|
||||
@@ -34,6 +34,7 @@ export class ElectronPlatformService extends PlatformService {
|
||||
private electron: ElectronService,
|
||||
private zone: NgZone,
|
||||
private shellIntegration: ShellIntegrationService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
this.configPath = path.join(electron.app.getPath('userData'), 'config.yaml')
|
||||
@@ -204,7 +205,7 @@ export class ElectronPlatformService extends PlatformService {
|
||||
const result = await this.electron.dialog.showOpenDialog(
|
||||
this.hostWindow.getWindow(),
|
||||
{
|
||||
buttonLabel: 'Select',
|
||||
buttonLabel: this.translate.instant('Select'),
|
||||
properties,
|
||||
},
|
||||
)
|
||||
|
@@ -2,7 +2,7 @@ import type { AppUpdater } from 'electron-updater'
|
||||
import { Injectable } from '@angular/core'
|
||||
import axios from 'axios'
|
||||
|
||||
import { Logger, LogService, ConfigService, UpdaterService, PlatformService } from 'tabby-core'
|
||||
import { Logger, LogService, ConfigService, UpdaterService, PlatformService, TranslateService } from 'tabby-core'
|
||||
import { ElectronService } from '../services/electron.service'
|
||||
|
||||
const UPDATES_URL = 'https://api.github.com/repos/eugeny/tabby/releases/latest'
|
||||
@@ -18,6 +18,7 @@ export class ElectronUpdaterService extends UpdaterService {
|
||||
constructor (
|
||||
log: LogService,
|
||||
config: ConfigService,
|
||||
private translate: TranslateService,
|
||||
private platform: PlatformService,
|
||||
private electron: ElectronService,
|
||||
) {
|
||||
@@ -132,8 +133,11 @@ export class ElectronUpdaterService extends UpdaterService {
|
||||
if ((await this.platform.showMessageBox(
|
||||
{
|
||||
type: 'warning',
|
||||
message: 'Installing the update will close all tabs and restart Tabby.',
|
||||
buttons: ['Update', 'Cancel'],
|
||||
message: this.translate.instant('Installing the update will close all tabs and restart Tabby.'),
|
||||
buttons: [
|
||||
this.translate.instant('Update'),
|
||||
this.translate.instant('Cancel'),
|
||||
],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-linkifier",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"version": "1.0.170-nightly.0",
|
||||
"description": "Makes URLs, IPs and file paths clickable in Tabby",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { ConfigService, PlatformService } from 'tabby-core'
|
||||
import { ConfigService, PlatformService, TranslateService } from 'tabby-core'
|
||||
import { TerminalDecorator, BaseTerminalTabComponent } from 'tabby-terminal'
|
||||
|
||||
import { LinkHandler } from './api'
|
||||
@@ -9,6 +9,7 @@ export class LinkHighlighterDecorator extends TerminalDecorator {
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
private platform: PlatformService,
|
||||
private translate: TranslateService,
|
||||
@Inject(LinkHandler) private handlers: LinkHandler[],
|
||||
) {
|
||||
super()
|
||||
@@ -42,13 +43,13 @@ export class LinkHighlighterDecorator extends TerminalDecorator {
|
||||
this.platform.popupContextMenu([
|
||||
{
|
||||
click: () => openLink(uri),
|
||||
label: 'Open',
|
||||
label: this.translate.instant('Open'),
|
||||
},
|
||||
{
|
||||
click: async () => {
|
||||
this.platform.setClipboard({ text: await getLink(uri) })
|
||||
},
|
||||
label: 'Copy',
|
||||
label: this.translate.instant('Copy'),
|
||||
},
|
||||
])
|
||||
return false
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-local",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"version": "1.0.170-nightly.0",
|
||||
"description": "Tabby's local shell plugin",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ToolbarButtonProvider, ToolbarButton } from 'tabby-core'
|
||||
import { ToolbarButtonProvider, ToolbarButton, TranslateService } from 'tabby-core'
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
|
||||
/** @hidden */
|
||||
@@ -8,6 +8,7 @@ import { TerminalService } from './services/terminal.service'
|
||||
export class ButtonProvider extends ToolbarButtonProvider {
|
||||
constructor (
|
||||
private terminal: TerminalService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -16,7 +17,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
||||
return [
|
||||
{
|
||||
icon: require('./icons/plus.svg'),
|
||||
title: 'New terminal',
|
||||
title: this.translate.instant('New terminal'),
|
||||
touchBarNSImage: 'NSTouchBarAddDetailTemplate',
|
||||
click: () => {
|
||||
this.terminal.openTab()
|
||||
|
@@ -1,6 +1,6 @@
|
||||
ng-container(*ngIf='!argvMode')
|
||||
.form-group
|
||||
label Command line
|
||||
label(translate) Command line
|
||||
.input-group
|
||||
.input-group-prepend
|
||||
a.input-group-text(
|
||||
@@ -16,7 +16,7 @@ ng-container(*ngIf='!argvMode')
|
||||
|
||||
ng-container(*ngIf='argvMode')
|
||||
.form-group
|
||||
label Program
|
||||
label(translate) Program
|
||||
.input-group
|
||||
.input-group-prepend
|
||||
a.input-group-text(
|
||||
@@ -31,7 +31,7 @@ ng-container(*ngIf='argvMode')
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Arguments
|
||||
label(translate) Arguments
|
||||
.input-group(
|
||||
*ngFor='let arg of _model.args; index as i; trackBy: trackByIndex',
|
||||
)
|
||||
@@ -46,4 +46,4 @@ ng-container(*ngIf='argvMode')
|
||||
.mt-2
|
||||
button.btn.btn-secondary((click)='_model.args.push("")')
|
||||
i.fas.fa-plus.mr-2
|
||||
| Add
|
||||
span(translate) Add
|
||||
|
@@ -11,10 +11,10 @@
|
||||
.d-flex
|
||||
button.btn.btn-secondary((click)='addEnvironmentVar()')
|
||||
i.fas.fa-plus.mr-2
|
||||
span Add
|
||||
span(translate) Add
|
||||
|
||||
.ml-auto
|
||||
.text-muted Substitutions allowed.
|
||||
.text-muted(translate) Substitutions allowed.
|
||||
.d-flex.ml-1(*ngIf='shouldShowExample()')
|
||||
.text-muted Example:
|
||||
.text-muted(translate) Example:
|
||||
a.ml-1((click)='addExample()', href='#') extend PATH
|
||||
|
@@ -2,13 +2,13 @@ command-line-editor([model]='profile.options')
|
||||
|
||||
.form-line(*ngIf='uac.isAvailable')
|
||||
.header
|
||||
.title Run as administrator
|
||||
.title(translate) Run as administrator
|
||||
toggle(
|
||||
[(ngModel)]='profile.options.runAsAdministrator',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Working directory
|
||||
label(translate) Working directory
|
||||
|
||||
.input-group
|
||||
input.form-control(
|
||||
@@ -21,7 +21,7 @@ command-line-editor([model]='profile.options')
|
||||
i.fas.fa-folder-open
|
||||
|
||||
.form-group
|
||||
label Environment
|
||||
label(translate) Environment
|
||||
environment-editor(
|
||||
type='text',
|
||||
[(model)]='profile.options.env',
|
||||
|
@@ -1,9 +1,9 @@
|
||||
h3.mb-3 Shell
|
||||
h3.mb-3(translate) Shell
|
||||
|
||||
.form-line(*ngIf='isConPTYAvailable')
|
||||
.header
|
||||
.title Use ConPTY
|
||||
.description Enables the experimental Windows ConPTY API
|
||||
.title(translate) Use ConPTY
|
||||
.description(translate) Enables the experimental Windows ConPTY API
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.useConPTY',
|
||||
@@ -11,7 +11,7 @@ h3.mb-3 Shell
|
||||
)
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.useConPTY && isConPTYAvailable && !isConPTYStable')
|
||||
.mr-auto Windows 10 build 18309 or above is recommended for ConPTY
|
||||
.mr-auto(translate) Windows 10 build 18309 or above is recommended for ConPTY
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.profile.startsWith("WSL") && (!config.store.terminal.useConPTY)')
|
||||
.mr-auto WSL terminal only supports TrueColor with ConPTY
|
||||
.mr-auto(translate) WSL terminal only supports TrueColor with ConPTY
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Component, Input, Injector } from '@angular/core'
|
||||
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabby-core'
|
||||
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, GetRecoveryTokenOptions } from 'tabby-core'
|
||||
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
||||
import { LocalProfile, SessionOptions } from '../api'
|
||||
import { Session } from '../session'
|
||||
@@ -74,7 +74,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
||||
this.recoveryStateChangedHint.next()
|
||||
}
|
||||
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
async getRecoveryToken (options?: GetRecoveryTokenOptions): Promise<any> {
|
||||
const cwd = this.session ? await this.session.getWorkingDirectory() : null
|
||||
return {
|
||||
type: 'app:local-tab',
|
||||
@@ -83,10 +83,10 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
||||
options: {
|
||||
...this.profile.options,
|
||||
cwd: cwd ?? this.profile.options.cwd,
|
||||
restoreFromPTYID: this.session?.getPTYID(),
|
||||
restoreFromPTYID: options?.includeState && this.session?.getPTYID(),
|
||||
},
|
||||
},
|
||||
savedState: this.frontend?.saveState(),
|
||||
savedState: options?.includeState && this.frontend?.saveState(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,8 +108,14 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
||||
return (await this.platform.showMessageBox(
|
||||
{
|
||||
type: 'warning',
|
||||
message: `"${children[0].command}" is still running. Close?`,
|
||||
buttons: ['Kill', 'Cancel'],
|
||||
message: this.translate.instant(
|
||||
'"{command}" is still running. Close?',
|
||||
children[0],
|
||||
),
|
||||
buttons: [
|
||||
this.translate.instant('Kill'),
|
||||
this.translate.instant('Cancel'),
|
||||
],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
|
||||
import { HotkeyDescription, HotkeyProvider, TranslateService } from 'tabby-core'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@@ -7,10 +7,12 @@ export class LocalTerminalHotkeyProvider extends HotkeyProvider {
|
||||
hotkeys: HotkeyDescription[] = [
|
||||
{
|
||||
id: 'new-tab',
|
||||
name: 'New tab',
|
||||
name: this.translate.instant('New tab'),
|
||||
},
|
||||
]
|
||||
|
||||
constructor (private translate: TranslateService) { super() }
|
||||
|
||||
async provide (): Promise<HotkeyDescription[]> {
|
||||
return this.hotkeys
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import deepClone from 'clone-deep'
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { ProfileProvider, NewTabParameters, ConfigService, SplitTabComponent, AppService, PartialProfile } from 'tabby-core'
|
||||
import { ProfileProvider, NewTabParameters, ConfigService, SplitTabComponent, AppService, PartialProfile, TranslateService } from 'tabby-core'
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
|
||||
import { ShellProvider, Shell, SessionOptions, LocalProfile } from './api'
|
||||
@@ -8,7 +8,7 @@ import { ShellProvider, Shell, SessionOptions, LocalProfile } from './api'
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LocalProfilesService extends ProfileProvider<LocalProfile> {
|
||||
id = 'local'
|
||||
name = 'Local'
|
||||
name = this.translate.instant('Local terminal')
|
||||
settingsComponent = LocalProfileSettingsComponent
|
||||
configDefaults = {
|
||||
options: {
|
||||
@@ -29,6 +29,7 @@ export class LocalProfilesService extends ProfileProvider<LocalProfile> {
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private config: ConfigService,
|
||||
private translate: TranslateService,
|
||||
@Inject(ShellProvider) private shellProviders: ShellProvider[],
|
||||
) {
|
||||
super()
|
||||
|
@@ -19,18 +19,4 @@ export class RecoveryProvider extends TabRecoveryProvider<TerminalTabComponent>
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
duplicate (recoveryToken: RecoveryToken): RecoveryToken {
|
||||
return {
|
||||
...recoveryToken,
|
||||
profile: {
|
||||
...recoveryToken.profile,
|
||||
options: {
|
||||
...recoveryToken.profile.options,
|
||||
restoreFromPTYID: null,
|
||||
},
|
||||
},
|
||||
savedState: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { NgZone, Injectable } from '@angular/core'
|
||||
import { ConfigService, HostAppService, Platform, ProfilesService } from 'tabby-core'
|
||||
import { ConfigService, HostAppService, Platform, ProfilesService, TranslateService } from 'tabby-core'
|
||||
import { ElectronService } from 'tabby-electron'
|
||||
|
||||
/** @hidden */
|
||||
@@ -13,6 +13,7 @@ export class DockMenuService {
|
||||
private hostApp: HostAppService,
|
||||
private zone: NgZone,
|
||||
private profilesService: ProfilesService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
config.changed$.subscribe(() => this.update())
|
||||
}
|
||||
@@ -21,7 +22,7 @@ export class DockMenuService {
|
||||
if (this.hostApp.platform === Platform.Windows) {
|
||||
this.electron.app.setJumpList(this.config.store.profiles.length ? [{
|
||||
type: 'custom',
|
||||
name: 'Profiles',
|
||||
name: this.translate.instant('Profiles'),
|
||||
items: this.config.store.profiles.map(profile => ({
|
||||
type: 'task',
|
||||
program: process.execPath,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import * as fs from 'mz/fs'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HostAppService, Platform, LogService, Logger } from 'tabby-core'
|
||||
import { HostAppService, Platform, LogService, Logger, TranslateService } from 'tabby-core'
|
||||
|
||||
import { ShellProvider, Shell } from '../api'
|
||||
|
||||
@@ -11,6 +11,7 @@ export class LinuxDefaultShellProvider extends ShellProvider {
|
||||
|
||||
constructor (
|
||||
private hostApp: HostAppService,
|
||||
private translate: TranslateService,
|
||||
log: LogService,
|
||||
) {
|
||||
super()
|
||||
@@ -27,14 +28,14 @@ export class LinuxDefaultShellProvider extends ShellProvider {
|
||||
this.logger.warn('Could not detect user shell')
|
||||
return [{
|
||||
id: 'default',
|
||||
name: 'User default',
|
||||
name: this.translate.instant('User default'),
|
||||
command: '/bin/sh',
|
||||
env: {},
|
||||
}]
|
||||
} else {
|
||||
return [{
|
||||
id: 'default',
|
||||
name: 'User default',
|
||||
name: this.translate.instant('User default'),
|
||||
command: line.split(':')[6],
|
||||
args: ['--login'],
|
||||
hidden: true,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
|
||||
import { HostAppService, Platform } from 'tabby-core'
|
||||
import { HostAppService, Platform, TranslateService } from 'tabby-core'
|
||||
|
||||
import { ShellProvider, Shell } from '../api'
|
||||
|
||||
@@ -11,6 +11,7 @@ export class MacOSDefaultShellProvider extends ShellProvider {
|
||||
|
||||
constructor (
|
||||
private hostApp: HostAppService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -21,7 +22,7 @@ export class MacOSDefaultShellProvider extends ShellProvider {
|
||||
}
|
||||
return [{
|
||||
id: 'default',
|
||||
name: 'OS default',
|
||||
name: this.translate.instant('OS default'),
|
||||
command: await this.getDefaultShellCached(),
|
||||
args: ['--login'],
|
||||
hidden: true,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HostAppService, Platform } from 'tabby-core'
|
||||
import { HostAppService, Platform, TranslateService } from 'tabby-core'
|
||||
|
||||
import { ShellProvider, Shell } from '../api'
|
||||
|
||||
@@ -17,6 +17,7 @@ export class WindowsDefaultShellProvider extends ShellProvider {
|
||||
wsl: WSLShellProvider,
|
||||
stock: WindowsStockShellsProvider,
|
||||
private hostApp: HostAppService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
this.providers = [
|
||||
@@ -39,7 +40,7 @@ export class WindowsDefaultShellProvider extends ShellProvider {
|
||||
return [{
|
||||
...shell,
|
||||
id: 'default',
|
||||
name: `OS default (${shell.name})`,
|
||||
name: this.translate.instant('OS default ({name})', shell),
|
||||
hidden: true,
|
||||
env: {},
|
||||
}]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent } from 'tabby-core'
|
||||
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent, TranslateService } from 'tabby-core'
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { UACService } from './services/uac.service'
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
@@ -13,6 +13,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
||||
private config: ConfigService,
|
||||
private ngbModal: NgbModal,
|
||||
private notifications: NotificationsService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -23,10 +24,10 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
||||
}
|
||||
const items: MenuItemOptions[] = [
|
||||
{
|
||||
label: 'Save as profile',
|
||||
label: this.translate.instant('Save as profile'),
|
||||
click: async () => {
|
||||
const modal = this.ngbModal.open(PromptModalComponent)
|
||||
modal.componentInstance.prompt = 'New profile name'
|
||||
modal.componentInstance.prompt = this.translate.instant('New profile name')
|
||||
const name = (await modal.result)?.value
|
||||
if (!name) {
|
||||
return
|
||||
@@ -44,7 +45,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
||||
profile,
|
||||
]
|
||||
this.config.save()
|
||||
this.notifications.info('Saved')
|
||||
this.notifications.info(this.translate.instant('Saved'))
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -63,6 +64,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
private profilesService: ProfilesService,
|
||||
private terminalService: TerminalService,
|
||||
private uac: UACService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -72,7 +74,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
const items: MenuItemOptions[] = [
|
||||
{
|
||||
label: 'New terminal',
|
||||
label: this.translate.instant('New terminal'),
|
||||
click: () => {
|
||||
if (tab instanceof TerminalTabComponent) {
|
||||
this.profilesService.openNewTabForProfile(tab.profile)
|
||||
@@ -82,7 +84,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'New with profile',
|
||||
label: this.translate.instant('New with profile'),
|
||||
submenu: profiles.map(profile => ({
|
||||
label: profile.name,
|
||||
click: async () => {
|
||||
@@ -98,7 +100,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
if (this.uac.isAvailable) {
|
||||
items.push({
|
||||
label: 'New admin tab',
|
||||
label: this.translate.instant('New admin tab'),
|
||||
submenu: profiles.map(profile => ({
|
||||
label: profile.name,
|
||||
click: () => {
|
||||
@@ -116,7 +118,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
if (tab instanceof TerminalTabComponent && tabHeader && this.uac.isAvailable) {
|
||||
items.push({
|
||||
label: 'Duplicate as administrator',
|
||||
label: this.translate.instant('Duplicate as administrator'),
|
||||
click: () => {
|
||||
this.profilesService.openNewTabForProfile({
|
||||
...tab.profile,
|
||||
@@ -131,7 +133,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
if (tab instanceof TerminalTabComponent && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
|
||||
items.push({
|
||||
label: 'Focus all panes',
|
||||
label: this.translate.instant('Focus all panes'),
|
||||
click: () => {
|
||||
tab.focusAllPanes()
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-plugin-manager",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"version": "1.0.170-nightly.0",
|
||||
"description": "Tabby's plugin manager",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
@@ -1,16 +1,16 @@
|
||||
.d-flex.mb-3
|
||||
h3 Plugins
|
||||
h3(translate) Plugins
|
||||
button.btn.btn-secondary.btn-sm.ml-auto((click)='openPluginsFolder()')
|
||||
i.fas.fa-folder
|
||||
span Plugins folder
|
||||
span(translate) Plugins folder
|
||||
|
||||
.alert.alert-danger(*ngIf='errorMessage')
|
||||
strong Error in {{erroredPlugin}}:
|
||||
strong {{ 'Error in {plugin}:'|translate:{plugin: erroredPlugin} }}
|
||||
pre {{errorMessage}}
|
||||
|
||||
ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Available
|
||||
a(ngbNavLink, translate) Available
|
||||
ng-template(ngbNavContent)
|
||||
.input-group.mb-3.mt-3
|
||||
.input-group-prepend
|
||||
@@ -40,14 +40,14 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
|
||||
)
|
||||
i.fas.fa-fw.fa-cloud-download(*ngIf='busy.get(plugin.name) != BusyState.Installing')
|
||||
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Installing')
|
||||
span.ml-2 Get
|
||||
span.ml-2(translate) Get
|
||||
|
||||
button.btn.btn-secondary.btn-block.justify-content-center(
|
||||
*ngIf='plugin.homepage',
|
||||
(click)='showPluginHomepage(plugin)'
|
||||
)
|
||||
i.fas.fa-fw.fa-external-link-alt
|
||||
span.ml-2 Homepage
|
||||
span.ml-2(translate) Homepage
|
||||
|
||||
.col-8
|
||||
ng-container(*ngTemplateOutlet='pluginInfo; context: { plugin }')
|
||||
@@ -55,7 +55,7 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
|
||||
.mt-2 {{plugin.description}}
|
||||
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Installed
|
||||
a(ngbNavLink, translate) Installed
|
||||
ng-template(ngbNavContent)
|
||||
ngb-accordion.mb-4([closeOthers]='true')
|
||||
ng-container(*ngFor='let plugin of pluginManager.installedPlugins')
|
||||
@@ -64,8 +64,8 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
|
||||
.text-left.mr-auto
|
||||
div
|
||||
strong {{plugin.name}}
|
||||
small.text-muted.ml-2(*ngIf='plugin.isBuiltin') Built-in
|
||||
small.text-warning.ml-2(*ngIf='!isPluginEnabled(plugin)') Disabled
|
||||
small.text-muted.ml-2(*ngIf='plugin.isBuiltin', translate) Built-in
|
||||
small.text-warning.ml-2(*ngIf='!isPluginEnabled(plugin)', translate) Disabled
|
||||
small.d-block.text-muted {{plugin.description}}
|
||||
|
||||
button.btn.btn-primary.ml-2(
|
||||
@@ -75,7 +75,7 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
|
||||
)
|
||||
i.fas.fa-fw.fa-arrow-up(*ngIf='busy.get(plugin.name) != BusyState.Installing')
|
||||
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Installing')
|
||||
span Upgrade to {{knownUpgrades[plugin.name].version}}
|
||||
span {{ 'Upgrade to {version}'|translate:{version: knownUpgrades[plugin.name].version} }}
|
||||
|
||||
ng-template(ngbPanelContent)
|
||||
.row
|
||||
@@ -83,12 +83,14 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
|
||||
button.btn.btn-warning.btn-block.justify-content-center(
|
||||
(click)='togglePlugin(plugin)',
|
||||
*ngIf='isPluginEnabled(plugin)',
|
||||
[disabled]='!canDisablePlugin(plugin)'
|
||||
[disabled]='!canDisablePlugin(plugin)',
|
||||
translate
|
||||
) Disable
|
||||
|
||||
button.btn.btn-success.btn-block.justify-content-center(
|
||||
(click)='togglePlugin(plugin)',
|
||||
*ngIf='canDisablePlugin(plugin) && !isPluginEnabled(plugin)'
|
||||
*ngIf='canDisablePlugin(plugin) && !isPluginEnabled(plugin)',
|
||||
translate
|
||||
) Enable
|
||||
|
||||
button.btn.btn-danger.btn-block.justify-content-center(
|
||||
@@ -98,26 +100,25 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
|
||||
)
|
||||
i.fas.fa-fw.fa-trash(*ngIf='busy.get(plugin.name) != BusyState.Uninstalling')
|
||||
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Uninstalling')
|
||||
span Uninstall
|
||||
span(translate) Uninstall
|
||||
.col-8
|
||||
ng-container(*ngTemplateOutlet='pluginInfo; context: { plugin }')
|
||||
|
||||
|
||||
.mt-2 {{plugin.description}}
|
||||
|
||||
ng-template(#pluginInfo, let-plugin='plugin')
|
||||
.row.align-items-center
|
||||
.col-4
|
||||
strong Version
|
||||
strong(translate) Version
|
||||
.col-8
|
||||
span {{plugin.version}}
|
||||
.row.align-items-center
|
||||
.col-4
|
||||
strong Author
|
||||
strong(translate) Author
|
||||
.col-8
|
||||
.badge.badge-success(*ngIf='plugin.isOfficial')
|
||||
i.fas.fa-check
|
||||
span.ml-1 Official
|
||||
span.ml-1(translate) Official
|
||||
a.btn.btn-link.px-0.w-auto((click)='showPluginInfo(plugin)', *ngIf='!plugin.isOfficial')
|
||||
span {{plugin.author}}
|
||||
i.fas.fa-fw.fa-external-link-alt.ml-2
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-serial",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"version": "1.0.170-nightly.0",
|
||||
"description": "Serial connections for Tabby",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
@@ -3,7 +3,7 @@ import SerialPort from 'serialport'
|
||||
import { LogService, NotificationsService, Profile } from 'tabby-core'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { Injector, NgZone } from '@angular/core'
|
||||
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
||||
import { BaseSession, LoginScriptsOptions, SessionMiddleware, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
||||
import { SerialService } from './services/serial.service'
|
||||
|
||||
export interface SerialProfile extends Profile {
|
||||
@@ -32,6 +32,14 @@ export interface SerialPortInfo {
|
||||
description?: string
|
||||
}
|
||||
|
||||
class SlowFeedMiddleware extends SessionMiddleware {
|
||||
feedFromTerminal (data: Buffer): void {
|
||||
for (const byte of data) {
|
||||
this.outputToSession.next(Buffer.from([byte]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SerialSession extends BaseSession {
|
||||
serial: SerialPort
|
||||
|
||||
@@ -50,13 +58,11 @@ export class SerialSession extends BaseSession {
|
||||
this.notifications = injector.get(NotificationsService)
|
||||
|
||||
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
||||
this.streamProcessor.outputToSession$.subscribe(data => {
|
||||
this.serial?.write(data.toString())
|
||||
})
|
||||
this.streamProcessor.outputToTerminal$.subscribe(data => {
|
||||
this.emitOutput(data)
|
||||
this.loginScriptProcessor?.feedFromSession(data)
|
||||
})
|
||||
this.middleware.push(this.streamProcessor)
|
||||
|
||||
if (this.profile.options.slowSend) {
|
||||
this.middleware.unshift(new SlowFeedMiddleware())
|
||||
}
|
||||
|
||||
this.setLoginScriptsOptions(profile.options)
|
||||
}
|
||||
@@ -110,7 +116,7 @@ export class SerialSession extends BaseSession {
|
||||
setTimeout(() => this.streamProcessor.start())
|
||||
|
||||
this.serial.on('readable', () => {
|
||||
this.streamProcessor.feedFromSession(this.serial.read())
|
||||
this.emitOutput(this.serial.read())
|
||||
})
|
||||
|
||||
this.serial.on('end', () => {
|
||||
@@ -124,17 +130,10 @@ export class SerialSession extends BaseSession {
|
||||
}
|
||||
|
||||
write (data: Buffer): void {
|
||||
if (!this.profile.options.slowSend) {
|
||||
this.streamProcessor.feedFromTerminal(data)
|
||||
} else {
|
||||
for (const byte of data) {
|
||||
this.streamProcessor.feedFromTerminal(Buffer.from([byte]))
|
||||
}
|
||||
}
|
||||
this.serial?.write(data.toString())
|
||||
}
|
||||
|
||||
async destroy (): Promise<void> {
|
||||
this.streamProcessor.close()
|
||||
this.serviceMessage.complete()
|
||||
await super.destroy()
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) General
|
||||
a(ngbNavLink, translate) General
|
||||
ng-template(ngbNavContent)
|
||||
.row
|
||||
.col-6(ng:if='hostApp.platform !== Platform.Web')
|
||||
.form-group
|
||||
label Device
|
||||
label(translate) Device
|
||||
input.form-control(
|
||||
type='text',
|
||||
alwaysVisibleTypeahead,
|
||||
@@ -16,7 +16,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
|
||||
.col-6
|
||||
.form-group
|
||||
label Baud Rate
|
||||
label(translate) Baud rate
|
||||
input.form-control(
|
||||
type='number',
|
||||
alwaysVisibleTypeahead,
|
||||
@@ -28,11 +28,11 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
stream-processing-settings([options]='profile.options')
|
||||
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Advanced
|
||||
a(ngbNavLink, translate) Advanced
|
||||
ng-template(ngbNavContent)
|
||||
.form-line
|
||||
.header
|
||||
.title Data bits
|
||||
.title(translate) Data bits
|
||||
input.form-control(
|
||||
type='number',
|
||||
placeholder='8',
|
||||
@@ -41,7 +41,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Stop bits
|
||||
.title(translate) Stop bits
|
||||
input.form-control(
|
||||
type='number',
|
||||
placeholder='1',
|
||||
@@ -50,12 +50,12 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Parity
|
||||
.title(translate) Parity
|
||||
select.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.options.parity'
|
||||
)
|
||||
option(value='none') None
|
||||
option(value='none', translate) None
|
||||
option(value='even') Even
|
||||
option(value='odd') Odd
|
||||
option(value='mark', ng:if='hostApp.platform === Platform.Windows') Mark
|
||||
@@ -83,12 +83,12 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Slow feed
|
||||
.description Sends data one byte at a time
|
||||
.title(translate) Slow feed
|
||||
.description(translate) Sends data one byte at a time
|
||||
toggle([(ngModel)]='profile.options.slowFeed')
|
||||
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Login scripts
|
||||
a(ngbNavLink, translate) Login scripts
|
||||
ng-template(ngbNavContent)
|
||||
login-scripts-settings([options]='profile.options')
|
||||
|
||||
|
@@ -9,8 +9,8 @@
|
||||
.mr-auto
|
||||
|
||||
button.btn.btn-sm.btn-link.mr-3((click)='changeBaudRate()', *ngIf='session && session.open && hostApp.platform !== Platform.Web')
|
||||
span Change baud rate
|
||||
span(translate) Change baud rate
|
||||
|
||||
button.btn.btn-sm.btn-link((click)='reconnect()', *ngIf='!session || !session.open')
|
||||
i.fas.fa-redo
|
||||
span Reconnect
|
||||
span(translate) Reconnect
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import colors from 'ansi-colors'
|
||||
import { Component, Injector } from '@angular/core'
|
||||
import { first } from 'rxjs'
|
||||
import { Platform, SelectorService } from 'tabby-core'
|
||||
import { GetRecoveryTokenOptions, Platform, SelectorService } from 'tabby-core'
|
||||
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
||||
import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
|
||||
|
||||
@@ -67,14 +67,13 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
||||
|
||||
const session = new SerialSession(this.injector, this.profile)
|
||||
this.setSession(session)
|
||||
this.write(`Connecting to `)
|
||||
|
||||
this.startSpinner('Connecting')
|
||||
this.startSpinner(this.translate.instant('Connecting'))
|
||||
|
||||
try {
|
||||
await this.session!.start()
|
||||
this.stopSpinner()
|
||||
session.emitServiceMessage('Port opened')
|
||||
session.emitServiceMessage(this.translate.instant('Port opened'))
|
||||
} catch (e) {
|
||||
this.stopSpinner()
|
||||
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
||||
@@ -89,7 +88,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
||||
this.session?.resize(this.size.columns, this.size.rows)
|
||||
})
|
||||
this.attachSessionHandler(this.session!.destroyed$, () => {
|
||||
this.write('Press any key to reconnect\r\n')
|
||||
this.write(this.translate.instant('Press any key to reconnect') + '\r\n')
|
||||
this.input$.pipe(first()).subscribe(() => {
|
||||
if (!this.session?.open) {
|
||||
this.reconnect()
|
||||
@@ -99,11 +98,11 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
||||
super.attachSessionHandlers()
|
||||
}
|
||||
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
async getRecoveryToken (options?: GetRecoveryTokenOptions): Promise<any> {
|
||||
return {
|
||||
type: 'app:serial-tab',
|
||||
profile: this.profile,
|
||||
savedState: this.frontend?.saveState(),
|
||||
savedState: options?.includeState && this.frontend?.saveState(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +113,12 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
||||
}
|
||||
|
||||
async changeBaudRate () {
|
||||
const rate = await this.selector.show('Baud rate', BAUD_RATES.map(x => ({
|
||||
name: x.toString(), result: x,
|
||||
})))
|
||||
const rate = await this.selector.show(
|
||||
this.translate.instant('Baud rate'),
|
||||
BAUD_RATES.map(x => ({
|
||||
name: x.toString(), result: x,
|
||||
})),
|
||||
)
|
||||
this.serialPort.update({ baudRate: rate })
|
||||
this.profile!.options.baudrate = rate
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
|
||||
import { HotkeyDescription, HotkeyProvider, TranslateService } from 'tabby-core'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@@ -7,14 +7,16 @@ export class SerialHotkeyProvider extends HotkeyProvider {
|
||||
hotkeys: HotkeyDescription[] = [
|
||||
{
|
||||
id: 'serial',
|
||||
name: 'Show Serial connections',
|
||||
name: this.translate.instant('Show Serial connections'),
|
||||
},
|
||||
{
|
||||
id: 'restart-serial-session',
|
||||
name: 'Restart current serial session',
|
||||
name: this.translate.instant('Restart current serial session'),
|
||||
},
|
||||
]
|
||||
|
||||
constructor (private translate: TranslateService) { super() }
|
||||
|
||||
async provide (): Promise<HotkeyDescription[]> {
|
||||
return this.hotkeys
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import SerialPort from 'serialport'
|
||||
import WSABinding from 'serialport-binding-webserialapi'
|
||||
import deepClone from 'clone-deep'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ProfileProvider, NewTabParameters, SelectorService, HostAppService, Platform } from 'tabby-core'
|
||||
import { ProfileProvider, NewTabParameters, SelectorService, HostAppService, Platform, TranslateService } from 'tabby-core'
|
||||
import { SerialProfileSettingsComponent } from './components/serialProfileSettings.component'
|
||||
import { SerialTabComponent } from './components/serialTab.component'
|
||||
import { SerialService } from './services/serial.service'
|
||||
@@ -12,7 +12,7 @@ import { BAUD_RATES, SerialProfile } from './api'
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SerialProfilesService extends ProfileProvider<SerialProfile> {
|
||||
id = 'serial'
|
||||
name = 'Serial'
|
||||
name = this.translate.instant('Serial')
|
||||
settingsComponent = SerialProfileSettingsComponent
|
||||
configDefaults = {
|
||||
options: {
|
||||
@@ -38,6 +38,7 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
|
||||
private selector: SelectorService,
|
||||
private serial: SerialService,
|
||||
private hostApp: HostAppService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
if (hostApp.platform === Platform.Web) {
|
||||
@@ -51,7 +52,7 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
|
||||
{
|
||||
id: `serial:web`,
|
||||
type: 'serial',
|
||||
name: 'Serial connection',
|
||||
name: this.translate.instant('Serial connection'),
|
||||
icon: 'fas fa-microchip',
|
||||
isBuiltin: true,
|
||||
} as SerialProfile,
|
||||
@@ -62,7 +63,7 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
|
||||
{
|
||||
id: `serial:template`,
|
||||
type: 'serial',
|
||||
name: 'Serial connection',
|
||||
name: this.translate.instant('Serial connection'),
|
||||
icon: 'fas fa-microchip',
|
||||
isBuiltin: true,
|
||||
isTemplate: true,
|
||||
@@ -70,7 +71,9 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
|
||||
...(await this.serial.listPorts()).map(p => ({
|
||||
id: `serial:port-${slugify(p.name).replace('.', '-')}`,
|
||||
type: 'serial',
|
||||
name: p.description ? `Serial: ${p.description}` : 'Serial',
|
||||
name: p.description ?
|
||||
this.translate.instant('Serial: {description}', p) :
|
||||
this.translate.instant('Serial'),
|
||||
icon: 'fas fa-microchip',
|
||||
isBuiltin: true,
|
||||
options: {
|
||||
@@ -83,9 +86,12 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
|
||||
async getNewTabParameters (profile: SerialProfile): Promise<NewTabParameters<SerialTabComponent>> {
|
||||
if (!profile.options.baudrate) {
|
||||
profile = deepClone(profile)
|
||||
profile.options.baudrate = await this.selector.show('Baud rate', BAUD_RATES.map(x => ({
|
||||
name: x.toString(), result: x,
|
||||
})))
|
||||
profile.options.baudrate = await this.selector.show(
|
||||
this.translate.instant('Baud rate'),
|
||||
BAUD_RATES.map(x => ({
|
||||
name: x.toString(), result: x,
|
||||
})),
|
||||
)
|
||||
}
|
||||
return {
|
||||
type: SerialTabComponent,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-settings",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"version": "1.0.170-nightly.0",
|
||||
"description": "Tabby terminal settings page",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
@@ -17,7 +17,7 @@
|
||||
"author": "Eugene Pankov",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"marked": "^3.0.2",
|
||||
"marked": "^4.0.8",
|
||||
"ngx-infinite-scroll": "^10.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ToolbarButtonProvider, ToolbarButton, AppService, HostAppService, HotkeysService } from 'tabby-core'
|
||||
import { ToolbarButtonProvider, ToolbarButton, AppService, HostAppService, HotkeysService, TranslateService } from 'tabby-core'
|
||||
|
||||
import { SettingsTabComponent } from './components/settingsTab.component'
|
||||
|
||||
@@ -10,6 +10,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
||||
hostApp: HostAppService,
|
||||
hotkeys: HotkeysService,
|
||||
private app: AppService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
hostApp.settingsUIRequest$.subscribe(() => this.open())
|
||||
@@ -24,7 +25,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
||||
provide (): ToolbarButton[] {
|
||||
return [{
|
||||
icon: require('./icons/cog.svg'),
|
||||
title: 'Settings',
|
||||
title: this.translate.instant('Settings'),
|
||||
touchBarNSImage: 'NSTouchBarComposeTemplate',
|
||||
weight: 10,
|
||||
click: (): void => this.open(),
|
||||
|
@@ -1,12 +1,12 @@
|
||||
h3.mb-3 Config sync
|
||||
h3.mb-3(translate) Config sync
|
||||
|
||||
ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Sync
|
||||
a(ngbNavLink, translate) Sync
|
||||
ng-template(ngbNavContent)
|
||||
.form-line
|
||||
.header
|
||||
.title Sync host
|
||||
.title(translate) Sync host
|
||||
|
||||
.input-group.w-50
|
||||
input.form-control(
|
||||
@@ -20,8 +20,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Secret sync token
|
||||
.description Get it from the Tabby Web settings window
|
||||
.title(translate) Secret sync token
|
||||
.description(translate) Get it from the Tabby Web settings window
|
||||
|
||||
.input-group
|
||||
input.form-control(
|
||||
@@ -38,16 +38,16 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
ng-container(*ngIf='config.store.configSync.token')
|
||||
.alert.alert-danger(*ngIf='connectionSuccessful === false')
|
||||
i.fas.fa-exclamation-triangle
|
||||
span.ml-2 Connection failed: {{connectionError}}
|
||||
span.ml-2(translate='Connection failed: {error}', [translateParams]='{error: connectionError}')
|
||||
|
||||
ng-container(*ngIf='connectionSuccessful')
|
||||
.form-line
|
||||
.header
|
||||
.title Configs
|
||||
.title(translate) Configs
|
||||
|
||||
div(*ngIf='configs === null')
|
||||
i.fas.fa-fw.fa-circle-notch.fa-spin
|
||||
span.ml-2 Loading configs...
|
||||
span.ml-2(translate) Loading configs...
|
||||
|
||||
ng-container(*ngIf='configs !== null')
|
||||
.list-group-light
|
||||
@@ -59,34 +59,34 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
i.fas.fa-fw.fa-file
|
||||
.ml-2.d-flex.flex-column.align-items-start
|
||||
div {{cfg.name}}
|
||||
small.text-muted Modified on {{cfg.modified_at|date:'medium'}}
|
||||
small.text-muted {{ 'Modified on {date}'|translate:{date: cfg.modified_at|date:"medium"} }}
|
||||
.mr-auto
|
||||
button.btn.btn-link.ml-1(
|
||||
(click)='uploadAndSync(cfg)',
|
||||
[class.hover-reveal]='!isActiveConfig(cfg)'
|
||||
)
|
||||
i.fas.fa-arrow-up
|
||||
span.ml-2(*ngIf='isActiveConfig(cfg)') Upload
|
||||
span.ml-2(*ngIf='!isActiveConfig(cfg)') Replace
|
||||
span.ml-2(*ngIf='isActiveConfig(cfg)', translate) Upload
|
||||
span.ml-2(*ngIf='!isActiveConfig(cfg)', translate) Replace
|
||||
button.btn.btn-link.ml-1(
|
||||
(click)='downloadAndSync(cfg)',
|
||||
[class.hover-reveal]='!isActiveConfig(cfg)'
|
||||
)
|
||||
i.fas.fa-arrow-down
|
||||
span.ml-2 Download
|
||||
span.ml-2(translate) Download
|
||||
a.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||
href='#',
|
||||
(click)='uploadAsNew()'
|
||||
)
|
||||
i.fas.fa-fw
|
||||
i.fas.fa-fw.fa-cloud-upload-alt
|
||||
.ml-2 Upload as a new config
|
||||
.ml-2(translate) Upload as a new config
|
||||
|
||||
ng-container(*ngIf='hasMatchingRemoteConfig()')
|
||||
.form-line
|
||||
.header
|
||||
.title Sync automatically
|
||||
.description Automatically upload changes and check for updates every minute
|
||||
.title(translate) Sync automatically
|
||||
.description(translate) Automatically upload changes and check for updates every minute
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.configSync.auto',
|
||||
@@ -94,27 +94,30 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
)
|
||||
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Advanced
|
||||
a(ngbNavLink, translate) Advanced
|
||||
ng-template(ngbNavContent)
|
||||
.form-line
|
||||
.alert.alert-info(*ngIf='config.store.encrypted')
|
||||
div(translate) Partial config sync is not possible when the config is encrypted via Vault.
|
||||
|
||||
.form-line(*ngIf='!config.store.encrypted')
|
||||
.header
|
||||
.title Sync hotkeys
|
||||
.title(translate) Sync hotkeys
|
||||
toggle(
|
||||
[(ngModel)]='config.store.configSync.parts.hotkeys',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.form-line(*ngIf='!config.store.encrypted')
|
||||
.header
|
||||
.title Sync window settings
|
||||
.title(translate) Sync window settings
|
||||
toggle(
|
||||
[(ngModel)]='config.store.configSync.parts.appearance',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.form-line(*ngIf='!config.store.encrypted')
|
||||
.header
|
||||
.title Sync Vault
|
||||
.title(translate) Sync Vault
|
||||
toggle(
|
||||
[(ngModel)]='config.store.configSync.parts.vault',
|
||||
(ngModelChange)='config.save()',
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component, HostBinding } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { BaseComponent, ConfigService, PromptModalComponent, HostAppService, PlatformService, NotificationsService } from 'tabby-core'
|
||||
import { BaseComponent, ConfigService, PromptModalComponent, HostAppService, PlatformService, NotificationsService, TranslateService } from 'tabby-core'
|
||||
import { Config, ConfigSyncService } from '../services/configSync.service'
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
|
||||
private hostApp: HostAppService,
|
||||
private ngbModal: NgbModal,
|
||||
private notifications: NotificationsService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -54,9 +55,9 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
|
||||
}
|
||||
|
||||
async uploadAsNew () {
|
||||
let name = `New config on ${this.hostApp.platform}`
|
||||
let name = this.translate.instant('New config on {platform}', this.hostApp)
|
||||
const modal = this.ngbModal.open(PromptModalComponent)
|
||||
modal.componentInstance.prompt = 'Name for the new config'
|
||||
modal.componentInstance.prompt = this.translate.instant('Name for the new config')
|
||||
modal.componentInstance.value = name
|
||||
name = (await modal.result)?.value
|
||||
if (!name) {
|
||||
@@ -72,8 +73,11 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
|
||||
if (this.config.store.configSync.configID !== cfg.id) {
|
||||
if ((await this.platform.showMessageBox({
|
||||
type: 'warning',
|
||||
message: 'Overwrite the config on the remote side and start syncing?',
|
||||
buttons: ['Overwrite remote and sync', 'Cancel'],
|
||||
message: this.translate.instant('Overwrite the config on the remote side and start syncing?'),
|
||||
buttons: [
|
||||
this.translate.instant('Overwrite remote and sync'),
|
||||
this.translate.instant('Cancel'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
})).response === 1) {
|
||||
@@ -83,14 +87,17 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
|
||||
this.configSync.setConfig(cfg)
|
||||
await this.configSync.upload()
|
||||
this.loadConfigs()
|
||||
this.notifications.info('Config uploaded')
|
||||
this.notifications.info(this.translate.instant('Config uploaded'))
|
||||
}
|
||||
|
||||
async downloadAndSync (cfg: Config) {
|
||||
if ((await this.platform.showMessageBox({
|
||||
type: 'warning',
|
||||
message: 'Overwrite the local config and start syncing?',
|
||||
buttons: ['Overwrite local and sync', 'Cancel'],
|
||||
message: this.translate.instant('Overwrite the local config and start syncing?'),
|
||||
buttons: [
|
||||
this.translate.instant('Overwrite local and sync'),
|
||||
this.translate.instant('Cancel'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
})).response === 1) {
|
||||
@@ -98,7 +105,7 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
|
||||
}
|
||||
this.configSync.setConfig(cfg)
|
||||
await this.configSync.download()
|
||||
this.notifications.info('Config downloaded')
|
||||
this.notifications.info(this.translate.instant('Config downloaded'))
|
||||
}
|
||||
|
||||
hasMatchingRemoteConfig () {
|
||||
|
@@ -2,13 +2,13 @@
|
||||
h3.m-0 {{profile.name}}
|
||||
|
||||
.modal-header(*ngIf='defaultsMode')
|
||||
h3.m-0 Defaults for {{profileProvider.name}}
|
||||
h3.m-0 {{ 'Defaults for {type}'|translate:{type: profileProvider.name} }}
|
||||
|
||||
.modal-body
|
||||
.row
|
||||
.col-12.col-lg-4
|
||||
.form-group(*ngIf='!defaultsMode')
|
||||
label Name
|
||||
label(translate) Name
|
||||
input.form-control(
|
||||
type='text',
|
||||
autofocus,
|
||||
@@ -16,7 +16,7 @@
|
||||
)
|
||||
|
||||
.form-group(*ngIf='!defaultsMode')
|
||||
label Group
|
||||
label(translate) Group
|
||||
input.form-control(
|
||||
type='text',
|
||||
alwaysVisibleTypeahead,
|
||||
@@ -26,7 +26,7 @@
|
||||
)
|
||||
|
||||
.form-group(*ngIf='!defaultsMode')
|
||||
label Icon
|
||||
label(translate) Icon
|
||||
.input-group
|
||||
input.form-control(
|
||||
type='text',
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Color
|
||||
.title(translate) Color
|
||||
input.form-control.w-50(
|
||||
type='text',
|
||||
[(ngModel)]='profile.color',
|
||||
@@ -56,8 +56,8 @@
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Disable dynamic tab title
|
||||
.description Connection name will be used instead
|
||||
.title(translate) Disable dynamic tab title
|
||||
.description(translate) Connection name will be used instead
|
||||
toggle([(ngModel)]='profile.disableDynamicTitle')
|
||||
|
||||
.mb-4
|
||||
@@ -66,5 +66,5 @@
|
||||
ng-template(#placeholder)
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-primary((click)='save()') Save
|
||||
button.btn.btn-danger((click)='cancel()') Cancel
|
||||
button.btn.btn-primary((click)='save()', translate) Save
|
||||
button.btn.btn-danger((click)='cancel()', translate) Cancel
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.modal-header
|
||||
h5 Press the key now
|
||||
h5(translate) Press the key now
|
||||
|
||||
.modal-body
|
||||
.input
|
||||
@@ -9,4 +9,4 @@
|
||||
div([style.width]='timeoutProgress + "%"')
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-primary((click)='close()') Cancel
|
||||
button.btn.btn-primary((click)='close()', translate) Cancel
|
||||
|
@@ -1,4 +1,4 @@
|
||||
h3.mb-3 Hotkeys
|
||||
h3.mb-3(translate) Hotkeys
|
||||
|
||||
.input-group.mb-4
|
||||
.input-group-prepend
|
||||
|
@@ -3,4 +3,4 @@
|
||||
.stroke(*ngFor='let stroke of item') {{stroke}}
|
||||
.remove((click)='removeItem(item)') ×
|
||||
|
||||
.add((click)='addItem()') Add...
|
||||
.add((click)='addItem()', translate) Add...
|
||||
|
@@ -1,12 +1,12 @@
|
||||
h3.mb-3 Profiles
|
||||
h3.mb-3(translate) Profiles
|
||||
|
||||
ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Profiles
|
||||
a(ngbNavLink, translate) Profiles
|
||||
ng-template(ngbNavContent)
|
||||
.form-line
|
||||
.header
|
||||
.title Default profile for new tabs
|
||||
.title(translate) Default profile for new tabs
|
||||
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.profile',
|
||||
@@ -30,7 +30,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
|
||||
button.btn.btn-primary.flex-shrink-0.ml-3((click)='newProfile()')
|
||||
i.fas.fa-fw.fa-plus
|
||||
| New profile
|
||||
span(translate) New profile
|
||||
|
||||
.list-group.mt-3.mb-3
|
||||
ng-container(*ngFor='let group of profileGroups')
|
||||
@@ -40,7 +40,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
)
|
||||
.fa.fa-fw.fa-chevron-right(*ngIf='group.collapsed')
|
||||
.fa.fa-fw.fa-chevron-down(*ngIf='!group.collapsed')
|
||||
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
|
||||
span.ml-3.mr-auto {{group.name || ("Ungrouped"|translate)}}
|
||||
button.btn.btn-sm.btn-link.hover-reveal.ml-2(
|
||||
*ngIf='group.editable && group.name',
|
||||
(click)='$event.stopPropagation(); editGroup(group)'
|
||||
@@ -88,12 +88,12 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}
|
||||
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Advanced
|
||||
a(ngbNavLink, translate) Advanced
|
||||
ng-template(ngbNavContent)
|
||||
.form-line(*ngIf='config.store.profiles.length > 0')
|
||||
.header
|
||||
.title Show recent profiles in selector
|
||||
.description Set to 0 to disable recent profiles
|
||||
.title(translate) Show recent profiles in selector
|
||||
.description(translate) Set to 0 to disable recent profiles
|
||||
|
||||
input.form-control(
|
||||
type='number',
|
||||
@@ -105,8 +105,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
|
||||
.form-line(*ngIf='config.store.profiles.length > 0')
|
||||
.header
|
||||
.title Show built-in profiles in selector
|
||||
.description If disabled, only custom profiles will show up in the profile selector
|
||||
.title(translate) Show built-in profiles in selector
|
||||
.description(translate) If disabled, only custom profiles will show up in the profile selector
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.showBuiltinProfiles',
|
||||
@@ -115,8 +115,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Default profile settings
|
||||
.description These apply to all profiles of a given type
|
||||
.title(translate) Default profile settings
|
||||
.description(translate) These apply to all profiles of a given type
|
||||
|
||||
.list-group.mt-3.mb-3
|
||||
a.list-group-item.list-group-item-action(
|
||||
|
@@ -3,7 +3,7 @@ import slugify from 'slugify'
|
||||
import deepClone from 'clone-deep'
|
||||
import { Component, HostBinding, Inject } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider } from 'tabby-core'
|
||||
import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider, TranslateService } from 'tabby-core'
|
||||
import { EditProfileModalComponent } from './editProfileModal.component'
|
||||
|
||||
interface ProfileGroup {
|
||||
@@ -35,6 +35,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
private selector: SelectorService,
|
||||
private ngbModal: NgbModal,
|
||||
private platform: PlatformService,
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
this.profileProviders.sort((a, b) => a.name.localeCompare(b.name))
|
||||
@@ -58,7 +59,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles]
|
||||
profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
|
||||
base = await this.selector.show(
|
||||
'Select a base profile to use as a template',
|
||||
this.translate.instant('Select a base profile to use as a template'),
|
||||
profiles.map(p => ({
|
||||
icon: p.icon,
|
||||
description: this.profilesService.getDescription(p) ?? undefined,
|
||||
@@ -72,14 +73,14 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
if (base.isTemplate) {
|
||||
profile.name = ''
|
||||
} else if (!base.isBuiltin) {
|
||||
profile.name = `${base.name} copy`
|
||||
profile.name = this.translate.instant('{name} copy', base)
|
||||
}
|
||||
profile.isBuiltin = false
|
||||
profile.isTemplate = false
|
||||
await this.showProfileEditModal(profile)
|
||||
if (!profile.name) {
|
||||
const cfgProxy = this.profilesService.getConfigProxyForProfile(profile)
|
||||
profile.name = this.profilesService.providerForProfile(profile)?.getSuggestedName(cfgProxy) ?? `${base.name} copy`
|
||||
profile.name = this.profilesService.providerForProfile(profile)?.getSuggestedName(cfgProxy) ?? this.translate.instant('{name} copy', base)
|
||||
}
|
||||
profile.id = `${profile.type}:custom:${slugify(profile.name)}:${uuidv4()}`
|
||||
this.config.store.profiles = [profile, ...this.config.store.profiles]
|
||||
@@ -122,8 +123,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
if ((await this.platform.showMessageBox(
|
||||
{
|
||||
type: 'warning',
|
||||
message: `Delete "${profile.name}"?`,
|
||||
buttons: ['Delete', 'Keep'],
|
||||
message: this.translate.instant('Delete "{name}"?', profile),
|
||||
buttons: [
|
||||
this.translate.instant('Delete'),
|
||||
this.translate.instant('Keep'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
}
|
||||
@@ -156,7 +160,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
this.profileGroups.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? -1)
|
||||
|
||||
this.profileGroups.push({
|
||||
name: 'Built-in',
|
||||
name: this.translate.instant('Built-in'),
|
||||
profiles: this.builtinProfiles,
|
||||
editable: false,
|
||||
collapsed: false,
|
||||
@@ -165,7 +169,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
|
||||
async editGroup (group: ProfileGroup): Promise<void> {
|
||||
const modal = this.ngbModal.open(PromptModalComponent)
|
||||
modal.componentInstance.prompt = 'New name'
|
||||
modal.componentInstance.prompt = this.translate.instant('New name')
|
||||
modal.componentInstance.value = group.name
|
||||
const result = await modal.result
|
||||
if (result) {
|
||||
@@ -181,8 +185,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
if ((await this.platform.showMessageBox(
|
||||
{
|
||||
type: 'warning',
|
||||
message: `Delete "${group.name}"?`,
|
||||
buttons: ['Delete', 'Keep'],
|
||||
message: this.translate.instant('Delete "{name}"?', group),
|
||||
buttons: [
|
||||
this.translate.instant('Delete'),
|
||||
this.translate.instant('Keep'),
|
||||
],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
}
|
||||
@@ -190,8 +197,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
if ((await this.platform.showMessageBox(
|
||||
{
|
||||
type: 'warning',
|
||||
message: `Delete the group's profiles?`,
|
||||
buttons: ['Move to "Ungrouped"', 'Delete'],
|
||||
message: this.translate.instant(`Delete the group's profiles?`),
|
||||
buttons: [
|
||||
this.translate.instant('Move to "Ungrouped"'),
|
||||
this.translate.instant('Delete'),
|
||||
],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
}
|
||||
@@ -224,10 +234,10 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
|
||||
getTypeLabel (profile: PartialProfile<Profile>): string {
|
||||
const name = this.profilesService.providerForProfile(profile)?.name
|
||||
if (name === 'Local') {
|
||||
if (name === this.translate.instant('Local terminal')) {
|
||||
return ''
|
||||
}
|
||||
return name ?? 'Unknown'
|
||||
return name ?? this.translate.instant('Unknown')
|
||||
}
|
||||
|
||||
getTypeColorClass (profile: PartialProfile<Profile>): string {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import axios from 'axios'
|
||||
import marked from 'marked'
|
||||
import { marked } from 'marked'
|
||||
import { Component } from '@angular/core'
|
||||
import { BaseTabComponent } from 'tabby-core'
|
||||
import { BaseTabComponent, TranslateService } from 'tabby-core'
|
||||
|
||||
export interface Release {
|
||||
name: string
|
||||
@@ -21,9 +21,9 @@ export class ReleaseNotesComponent extends BaseTabComponent {
|
||||
releases: Release[] = []
|
||||
lastPage = 1
|
||||
|
||||
constructor () {
|
||||
constructor (translate: TranslateService) {
|
||||
super()
|
||||
this.setTitle('Release notes')
|
||||
this.setTitle(translate.instant('Release notes'))
|
||||
this.loadReleases(1)
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
h3.modal-header.m-0.pb-0 Set master passphrase
|
||||
h3.modal-header.m-0.pb-0(translate) Set master passphrase
|
||||
.modal-body
|
||||
.mb-2 You can change it later, but it's unrecoverable if forgotten.
|
||||
.mb-2(translate) You can change it later, but it's unrecoverable if forgotten.
|
||||
.input-group
|
||||
input.form-control.form-control-lg(
|
||||
[type]='showPassphrase ? "text" : "password"',
|
||||
@@ -16,5 +16,5 @@ h3.modal-header.m-0.pb-0 Set master passphrase
|
||||
i.fas.fa-eye
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-primary((click)='ok()') Set passphrase
|
||||
button.btn.btn-danger((click)='cancel()') Cancel
|
||||
button.btn.btn-primary((click)='ok()', translate) Set passphrase
|
||||
button.btn.btn-danger((click)='cancel()', translate) Cancel
|
||||
|
@@ -3,7 +3,7 @@
|
||||
li(ngbNavItem='application')
|
||||
a(ngbNavLink)
|
||||
i.fas.fa-fw.fa-window-maximize.mr-2
|
||||
| Application
|
||||
span(translate) Application
|
||||
ng-template(ngbNavContent)
|
||||
.content-box
|
||||
.row
|
||||
@@ -23,59 +23,69 @@
|
||||
i.fas.fa-sync(
|
||||
[class.fa-spin]='checkingForUpdate'
|
||||
)
|
||||
span Check for updates
|
||||
span(translate) Check for updates
|
||||
|
||||
button.btn.btn-info.mt-3.mb-2(
|
||||
*ngIf='updateAvailable',
|
||||
(click)='updater.update()',
|
||||
)
|
||||
i.fas.fa-sync
|
||||
span Update
|
||||
span(translate) Update
|
||||
|
||||
.col-12.col-md-6
|
||||
.list-group.list-group-light.mb-5
|
||||
button.list-group-item.list-group-item-action.link-card((click)='homeBase.reportBug()')
|
||||
i.fas.fa-fw.fa-bug
|
||||
div
|
||||
div Report a problem
|
||||
small.text-muted Generate a pre-filled GitHub issue
|
||||
div(translate) Report a problem
|
||||
small.text-muted(translate) Generate a pre-filled GitHub issue
|
||||
|
||||
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscussions()')
|
||||
i.fas.fa-fw.fa-comments
|
||||
div
|
||||
div Ask a question
|
||||
small.text-muted On GitHub Discussions
|
||||
div(translate) Ask a question
|
||||
small.text-muted(translate) On GitHub Discussions
|
||||
|
||||
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openGitHub()')
|
||||
i.fab.fa-fw.fa-github
|
||||
div
|
||||
div GitHub
|
||||
small.text-muted Source code
|
||||
small.text-muted(translate) Source code
|
||||
|
||||
button.list-group-item.list-group-item-action.link-card((click)='showReleaseNotes()')
|
||||
i.fas.fa-fw.fa-book
|
||||
div
|
||||
div What's new
|
||||
small.text-muted Show release notes
|
||||
div(translate) What's new
|
||||
small.text-muted(translate) Show release notes
|
||||
|
||||
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openTwitter()')
|
||||
i.fab.fa-fw.fa-twitter
|
||||
div
|
||||
div Subscribe to updates
|
||||
small.text-muted Tabby news and updates on Twitter
|
||||
div(translate) Subscribe to updates
|
||||
small.text-muted(translate) Tabby news and updates on Twitter
|
||||
|
||||
|
||||
h3 Application settings
|
||||
h3(translate) Application settings
|
||||
.form-line
|
||||
.header
|
||||
.title(translate) Language
|
||||
select.form-control([(ngModel)]='config.store.language', (ngModelChange)='saveConfiguration()')
|
||||
option([ngValue]='null', translate) Automatic
|
||||
option(
|
||||
[value]='lang.code',
|
||||
*ngFor='let lang of locale.allLanguages'
|
||||
) {{lang.name|translate}}
|
||||
|
||||
.form-line(*ngIf='platform.isShellIntegrationSupported()')
|
||||
.header
|
||||
.title Shell integration
|
||||
.description Allows quickly opening a terminal in the selected folder
|
||||
.title(translate) Shell integration
|
||||
.description(translate) Allows quickly opening a terminal in the selected folder
|
||||
toggle([ngModel]='isShellIntegrationInstalled', (ngModelChange)='toggleShellIntegration()')
|
||||
|
||||
.form-line(*ngIf='hostApp.platform !== Platform.Web')
|
||||
.header
|
||||
.title Enable analytics
|
||||
.description We're only tracking your Tabby and OS versions.
|
||||
.title(translate) Enable analytics
|
||||
.description(translate) We're only tracking your Tabby and OS versions.
|
||||
toggle(
|
||||
[(ngModel)]='config.store.enableAnalytics',
|
||||
(ngModelChange)='saveConfiguration(true)',
|
||||
@@ -83,23 +93,23 @@
|
||||
|
||||
.form-line(*ngIf='hostApp.platform !== Platform.Web')
|
||||
.header
|
||||
.title Automatic Updates
|
||||
.description Enable automatic installation of updates when they become available.
|
||||
.title(translate) Automatic Updates
|
||||
.description(translate) Enable automatic installation of updates when they become available.
|
||||
toggle([(ngModel)]='config.store.enableAutomaticUpdates', (ngModelChange)='saveConfiguration()')
|
||||
|
||||
.form-line(*ngIf='hostApp.platform !== Platform.Web')
|
||||
.header
|
||||
.title Debugging
|
||||
.title(translate) Debugging
|
||||
|
||||
button.btn.btn-secondary((click)='hostWindow.openDevTools()')
|
||||
i.fas.fa-bug
|
||||
span Open DevTools
|
||||
span(translate) Open DevTools
|
||||
|
||||
ng-container(*ngFor='let provider of settingsProviders')
|
||||
li(*ngIf='provider.prioritized', [ngbNavItem]='provider.id')
|
||||
a(ngbNavLink)
|
||||
i(class='fas fa-fw mr-2 fa-{{provider.icon}}')
|
||||
| {{provider.title}}
|
||||
span(translate) {{provider.title}}
|
||||
ng-template(ngbNavContent)
|
||||
settings-tab-body([provider]='provider')
|
||||
|
||||
@@ -109,24 +119,24 @@
|
||||
li(*ngIf='!provider.prioritized', [ngbNavItem]='provider.id')
|
||||
a(ngbNavLink)
|
||||
i(class='fas fa-fw mr-2 fa-{{provider.icon || "puzzle-piece"}}')
|
||||
| {{provider.title}}
|
||||
span(translate) {{provider.title}}
|
||||
ng-template(ngbNavContent)
|
||||
settings-tab-body([provider]='provider')
|
||||
|
||||
li(ngbNavItem='config-file')
|
||||
a(ngbNavLink)
|
||||
i.fas.fa-fw.fa-code.mr-2
|
||||
| Config file
|
||||
span(translate) Config file
|
||||
ng-template.test(ngbNavContent)
|
||||
.d-flex.flex-column.w-100.h-100
|
||||
.h-100.d-flex
|
||||
.w-100.d-flex.flex-column
|
||||
h3 Config file
|
||||
h3(translate) Config file
|
||||
textarea.form-control.h-100(
|
||||
[(ngModel)]='configFile'
|
||||
)
|
||||
.w-100.d-flex.flex-column(*ngIf='showConfigDefaults')
|
||||
h3 Defaults
|
||||
h3(translate) Defaults
|
||||
textarea.form-control.h-100(
|
||||
[(ngModel)]='configDefaults',
|
||||
readonly
|
||||
@@ -134,20 +144,25 @@
|
||||
.mt-3.d-flex
|
||||
button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
|
||||
i.fas.fa-check.mr-2
|
||||
| Save and apply
|
||||
span(translate) Save and apply
|
||||
button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
|
||||
i.fas.fa-exclamation-triangle.mr-2
|
||||
| Invalid syntax
|
||||
span(translate) Invalid syntax
|
||||
button.btn.btn-secondary.ml-auto(
|
||||
(click)='showConfigDefaults = !showConfigDefaults'
|
||||
(click)='showConfigDefaults = !showConfigDefaults',
|
||||
translate
|
||||
) Show defaults
|
||||
button.btn.btn-secondary.ml-3(
|
||||
*ngIf='platform.getConfigPath()',
|
||||
(click)='showConfigFile()'
|
||||
)
|
||||
i.fas.fa-external-link-square-alt.mr-2
|
||||
| Show config file
|
||||
span(translate) Show config file
|
||||
|
||||
div([ngbNavOutlet]='nav')
|
||||
|
||||
button.btn.btn-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
|
||||
button.btn.btn-warning.btn-block(
|
||||
*ngIf='config.restartRequested',
|
||||
(click)='restartApp()',
|
||||
translate
|
||||
) Restart the app to apply changes
|
||||
|
@@ -12,6 +12,8 @@ import {
|
||||
PlatformService,
|
||||
HostWindowService,
|
||||
AppService,
|
||||
LocaleService,
|
||||
TranslateService,
|
||||
} from 'tabby-core'
|
||||
|
||||
import { SettingsTabProvider } from '../api'
|
||||
@@ -43,12 +45,14 @@ export class SettingsTabComponent extends BaseTabComponent {
|
||||
public homeBase: HomeBaseService,
|
||||
public platform: PlatformService,
|
||||
public zone: NgZone,
|
||||
public locale: LocaleService,
|
||||
private updater: UpdaterService,
|
||||
private app: AppService,
|
||||
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
||||
translate: TranslateService,
|
||||
) {
|
||||
super()
|
||||
this.setTitle('Settings')
|
||||
this.setTitle(translate.instant('Settings'))
|
||||
this.settingsProviders = config.enabledServices(this.settingsProviders)
|
||||
this.settingsProviders = this.settingsProviders.filter(x => !!x.getComponentType())
|
||||
this.settingsProviders.sort((a, b) => a.weight - b.weight + a.title.localeCompare(b.title))
|
||||
|
@@ -1,27 +1,27 @@
|
||||
.text-center(*ngIf='!vault.isEnabled()')
|
||||
i.fas.fa-key.fa-3x.m-3
|
||||
h3.m-3 Vault is not configured
|
||||
.m-3 Vault is an always-encrypted container for secrets such as SSH passwords and private key passphrases.
|
||||
button.btn.btn-primary.m-2((click)='enableVault()') Set master passphrase
|
||||
h3.m-3(translate) Vault is not configured
|
||||
.m-3(translate) Vault is an always-encrypted container for secrets such as SSH passwords and private key passphrases.
|
||||
button.btn.btn-primary.m-2((click)='enableVault()', translate) Set master passphrase
|
||||
|
||||
|
||||
div(*ngIf='vault.isEnabled()')
|
||||
.d-flex.align-items-center.mb-3
|
||||
h3.m-0 Vault
|
||||
h3.m-0(translate) Vault
|
||||
.d-flex.ml-auto(ngbDropdown, *ngIf='vault.isEnabled()')
|
||||
button.btn.btn-secondary(ngbDropdownToggle) Options
|
||||
button.btn.btn-secondary(ngbDropdownToggle, translate) Options
|
||||
div(ngbDropdownMenu)
|
||||
a(ngbDropdownItem, (click)='changePassphrase()')
|
||||
i.fas.fa-fw.fa-key
|
||||
span Change the master passphrase
|
||||
span(translate) Change the master passphrase
|
||||
a(ngbDropdownItem, (click)='disableVault()')
|
||||
i.fas.fa-fw.fa-radiation-alt
|
||||
span Erase the vault
|
||||
span(translate) Erase the Vault
|
||||
|
||||
div(*ngIf='vaultContents')
|
||||
.text-center(*ngIf='!vaultContents.secrets.length')
|
||||
i.fas.fa-empty-set.fa-3x
|
||||
h3.m-3 Vault is empty
|
||||
h3.m-3(translate) Vault is empty
|
||||
|
||||
.list-group
|
||||
.list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets')
|
||||
@@ -38,30 +38,30 @@ div(*ngIf='vault.isEnabled()')
|
||||
(click)='renameFile(secret)'
|
||||
)
|
||||
i.fas.fa-fw.fa-pencil-alt
|
||||
span Rename
|
||||
span(translate) Rename
|
||||
button(
|
||||
ngbDropdownItem,
|
||||
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
|
||||
(click)='replaceFileContent(secret)'
|
||||
)
|
||||
i.fas.fa-fw.fa-file-import
|
||||
span Replace
|
||||
span(translate) Replace
|
||||
button(
|
||||
ngbDropdownItem,
|
||||
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
|
||||
(click)='exportFile(secret)'
|
||||
)
|
||||
i.fas.fa-fw.fa-file-export
|
||||
span Export
|
||||
span(translate) Export
|
||||
button(ngbDropdownItem, (click)='removeSecret(secret)')
|
||||
i.fas.fa-fw.fa-trash
|
||||
span Delete
|
||||
span(translate) Delete
|
||||
|
||||
h3.mt-5 Options
|
||||
h3.mt-5(translate) Options
|
||||
.form-line
|
||||
.header
|
||||
.title Encrypt config file
|
||||
.description Puts all of Tabby's configuration into the vault
|
||||
.title(translate) Encrypt config file
|
||||
.description(translate) Puts all of Tabby's configuration into the vault
|
||||
toggle(
|
||||
[ngModel]='config.store.encrypted',
|
||||
(click)='toggleConfigEncrypted()',
|
||||
@@ -69,5 +69,5 @@ div(*ngIf='vault.isEnabled()')
|
||||
|
||||
.text-center(*ngIf='!vaultContents')
|
||||
i.fas.fa-key.fa-3x
|
||||
h3.m-3 Vault is locked
|
||||
button.btn.btn-primary.m-2((click)='loadVault()') Show vault contents
|
||||
h3.m-3(translate) Vault is locked
|
||||
button.btn.btn-primary.m-2((click)='loadVault()', translate) Show vault contents
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user