Compare commits

..

97 Commits

Author SHA1 Message Date
Eugene Pankov
ff49b9e38a settings: added release notes 2021-07-18 16:48:13 +02:00
Eugene Pankov
439e407595 serial: fixed missing port names in builtin profiles 2021-07-18 15:22:46 +02:00
Eugene Pankov
1eed32f8d8 local: UI to support single "command line" in profiles 2021-07-18 15:22:35 +02:00
Eugene Pankov
66098b5c6d Update hostWindow.service.ts 2021-07-18 14:08:20 +02:00
Eugene Pankov
a725d25e46 web: added proper platform detection and hotkey handling 2021-07-17 16:39:08 +02:00
Eugene Pankov
4e42dfd46b plugin version bump 2021-07-17 00:54:38 +02:00
Eugene Pankov
c2657568a6 updated web demo for new profiles 2021-07-17 00:52:16 +02:00
Eugene Pankov
dbe7b8cf56 plugin version bump 2021-07-17 00:52:07 +02:00
Eugene Pankov
a82a65ed46 plugin version bump 2021-07-16 23:59:45 +02:00
Eugene Pankov
893d9a9887 fixed button discoloration 2021-07-16 23:59:12 +02:00
Eugeny
1facd46901 Merge pull request #4216 from Eugeny/all-contributors/add-KingMob 2021-07-16 23:54:00 +02:00
allcontributors[bot]
7af89e1d07 docs: update .all-contributorsrc [skip ci] 2021-07-16 21:53:41 +00:00
Eugeny
50b2040d16 Merge pull request #4215 from KingMob/bugfix/get-shell-names-correctly 2021-07-16 23:53:40 +02:00
allcontributors[bot]
a65505c498 docs: update README.md [skip ci] 2021-07-16 21:53:40 +00:00
Matthew Davidson
7e8c19e97b Correctly name profiles for shells outside /usr
Shells outside /usr get named incorrectly. E.g., if I add /usr/local/bin/fish
to /etc/shells, when Tabby starts up, it think the profile name should be "local".
2021-07-16 16:50:32 -04:00
Eugeny
8ac101cf9c Merge pull request #4213 from Eugeny/dependabot/npm_and_yarn/electron-13.1.7 2021-07-16 11:31:54 +02:00
dependabot[bot]
6297987e4f Bump electron from 13.1.6 to 13.1.7
Bumps [electron](https://github.com/electron/electron) from 13.1.6 to 13.1.7.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v13.1.6...v13.1.7)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-16 04:07:07 +00:00
Eugene Pankov
cbd7c7c02f remove outline buttons 2021-07-14 00:21:23 +02:00
Eugene Pankov
57a198b082 typing cleanup 2021-07-14 00:03:05 +02:00
Eugene Pankov
e245629c5a ui tweak 2021-07-13 23:53:43 +02:00
Eugene Pankov
760311ffa0 lint 2021-07-13 23:50:31 +02:00
Eugene Pankov
2f13f3a401 strongly typed partial profiles wip 2021-07-13 23:44:23 +02:00
Eugene Pankov
5ddf36d4c1 lint 2021-07-13 22:23:46 +02:00
Eugene Pankov
a632a599d3 WSL: fixed multiple distro detection 2021-07-13 22:20:59 +02:00
Eugene Pankov
ca9f11484c move terminal-tab to use .profile 2021-07-13 21:47:06 +02:00
Eugene Pankov
9d224cbce2 Merge branch 'master' of github.com:Eugeny/terminus 2021-07-13 20:37:20 +02:00
Eugeny
7df36b89c3 Merge pull request #4198 from Eugeny/dependabot/npm_and_yarn/typescript-eslint/parser-4.28.3
Bump @typescript-eslint/parser from 4.28.2 to 4.28.3
2021-07-13 20:19:47 +02:00
Eugene Pankov
8b62aa24ea Merge branch 'master' of github.com:Eugeny/terminus 2021-07-13 20:19:31 +02:00
Eugene Pankov
9502240480 reworked login scripts - fixes #1349 2021-07-13 20:19:28 +02:00
dependabot[bot]
31efa2f9c1 Bump @typescript-eslint/parser from 4.28.2 to 4.28.3
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.28.2 to 4.28.3.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.3/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-13 17:53:20 +00:00
Eugeny
b40192f2ad Merge pull request #4196 from Eugeny/dependabot/npm_and_yarn/style-loader-3.1.0
Bump style-loader from 3.0.0 to 3.1.0
2021-07-13 19:48:29 +02:00
Eugeny
489ea5f891 Merge pull request #4197 from Eugeny/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-4.28.3 2021-07-13 19:46:30 +02:00
Eugeny
05eb24cd99 Merge pull request #4191 from Eugeny/dependabot/npm_and_yarn/typedoc-0.21.4
Bump typedoc from 0.21.2 to 0.21.4
2021-07-13 19:46:21 +02:00
Eugene Pankov
5053743b1b fixed dynamic port forward listener not getting cleaned up - fixes #4200 2021-07-13 19:27:02 +02:00
Eugene Pankov
6d7f25870e Merge branch 'master' of github.com:Eugeny/terminus 2021-07-13 19:26:21 +02:00
Eugeny
6cdee22164 Delete clink.html 2021-07-13 16:04:26 +02:00
Eugene Pankov
c043d5bc83 delegate getDescription to ProfilesService 2021-07-13 10:28:27 +02:00
Eugene Pankov
d7741f07a1 fixed configproxy 2021-07-13 10:28:08 +02:00
dependabot[bot]
14c0b8891d Bump @typescript-eslint/eslint-plugin from 4.28.2 to 4.28.3
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.28.2 to 4.28.3.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.3/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-13 04:04:47 +00:00
dependabot[bot]
ea1d8e95f3 Bump style-loader from 3.0.0 to 3.1.0
Bumps [style-loader](https://github.com/webpack-contrib/style-loader) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/webpack-contrib/style-loader/releases)
- [Changelog](https://github.com/webpack-contrib/style-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/style-loader/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: style-loader
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-13 04:04:14 +00:00
Eugene Pankov
50c20f08f8 fixed 'hide dock on blur' 2021-07-12 23:35:42 +02:00
Eugene Pankov
8f55333d23 fixed visual layout of readline prompts 2021-07-12 22:31:17 +02:00
Eugene Pankov
3be98e6244 fixed home/end keys in ssh sessions 2021-07-12 22:23:46 +02:00
Eugene Pankov
5e771534a8 wording 2021-07-12 21:29:38 +02:00
Eugene Pankov
ba33f18af7 fixed ssh connections 2021-07-12 21:29:34 +02:00
Eugene Pankov
82f3b61b5e bump 2021-07-12 21:29:28 +02:00
dependabot[bot]
f587fd279c Bump typedoc from 0.21.2 to 0.21.4
Bumps [typedoc](https://github.com/TypeStrong/TypeDoc) from 0.21.2 to 0.21.4.
- [Release notes](https://github.com/TypeStrong/TypeDoc/releases)
- [Commits](https://github.com/TypeStrong/TypeDoc/compare/v0.21.2...v0.21.4)

---
updated-dependencies:
- dependency-name: typedoc
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 04:05:50 +00:00
Eugene Pankov
8d13cb0fe8 bump 2021-07-11 23:04:48 +02:00
Eugene Pankov
d1a6baf858 bump 2021-07-11 23:03:55 +02:00
Eugene Pankov
25034342c3 bumped plugins 2021-07-11 23:02:27 +02:00
Eugene Pankov
379775bcd3 import fix 2021-07-11 23:02:23 +02:00
Eugene Pankov
ff18926bf9 WSA wip 2021-07-11 22:59:39 +02:00
Eugene Pankov
37e564130e web preload progress callback 2021-07-11 16:31:00 +02:00
Eugene Pankov
d8a8d41614 lint 2021-07-11 16:24:04 +02:00
Eugene Pankov
7db3335938 bump, include source maps in releases 2021-07-11 16:12:36 +02:00
Eugene Pankov
c1051379c1 add tabby-web-demo to the repo 2021-07-11 16:00:59 +02:00
Eugeny
f7a0fb488b Merge pull request #4175 from Eugeny/dependabot/npm_and_yarn/tabby-terminal/xterm-addon-ligatures-0.5.1
Bump xterm-addon-ligatures from 0.5.0 to 0.5.1 in /tabby-terminal
2021-07-11 12:39:02 +02:00
Eugene Pankov
2706045cc2 tab context menu option to save split layouts as profiles - fixes #3468 2021-07-11 12:38:35 +02:00
Eugene Pankov
908f90cd52 automatically clean up defaults from the config file 2021-07-11 00:06:52 +02:00
Eugene Pankov
67bbbd7f65 allow renaming and replacing files in the vault - fixes #4110 2021-07-10 21:31:18 +02:00
Eugene Pankov
0008b2f022 preserve file modes for up- and downloads - fixes #4141 2021-07-10 20:39:45 +02:00
Eugene Pankov
3e61630c6a added a way to switch a pane's profile - fixes #1007, fixes #2963, fixes #3082, fixes #4151 2021-07-10 20:05:31 +02:00
Eugene Pankov
6f912dc12b make sure ssh ident goes out in a single packet - #4167 2021-07-10 17:51:37 +02:00
Eugene Pankov
e1f2e176ce actually implement telnet protocol - fixes #4164 2021-07-10 16:09:24 +02:00
Eugene Pankov
f39b4c6dbe Update buttonProvider.ts 2021-07-10 14:47:41 +02:00
Eugene Pankov
c49ff68ed6 avoid duplicate entries in recent profiles - fixes #4179 2021-07-10 14:30:33 +02:00
Eugene Pankov
891cf42338 make ssh the default quick-connect profile - fixes #4181 2021-07-10 14:10:55 +02:00
Eugene Pankov
b9763044ee close streamProcessor in telnet tabs 2021-07-09 10:31:12 +02:00
Eugene Pankov
46e0035327 sort profiles by groups - fixes #4163 2021-07-09 10:02:58 +02:00
Eugene Pankov
6df8707b6d Merge branch 'master' of github.com:Eugeny/terminus 2021-07-09 09:58:35 +02:00
Eugene Pankov
24b7922539 fixed high contrast appearance for theme previews 2021-07-09 09:58:31 +02:00
Eugene Pankov
485665d449 fixed toggle switch appearance in windows high contrast mode - fixes #4165 2021-07-09 09:47:51 +02:00
Eugene Pankov
e09a011c23 hint about ssh connections being moved to profiles 2021-07-09 09:47:09 +02:00
Eugene Pankov
833a348fdb fixed matchedHotkey deprecation - fixes #4153, fixes #4156 2021-07-09 09:46:54 +02:00
Eugene Pankov
26ff6f17e7 fixed toolbar state loading 2021-07-09 09:10:10 +02:00
dependabot[bot]
d026e634e5 Bump xterm-addon-ligatures from 0.5.0 to 0.5.1 in /tabby-terminal
Bumps [xterm-addon-ligatures](https://github.com/xtermjs/xterm.js) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/xtermjs/xterm.js/releases)
- [Commits](https://github.com/xtermjs/xterm.js/commits)

---
updated-dependencies:
- dependency-name: xterm-addon-ligatures
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-09 04:25:46 +00:00
Eugeny
356a2f38b6 Merge pull request #4160 from Eugeny/dependabot/npm_and_yarn/sentry/cli-1.67.1 2021-07-08 22:04:39 +02:00
Eugeny
bdb37a9a18 Merge pull request #4169 from Eugeny/all-contributors/add-cypherbits 2021-07-08 22:04:29 +02:00
allcontributors[bot]
22d89041f8 docs: update .all-contributorsrc [skip ci] 2021-07-08 20:04:20 +00:00
allcontributors[bot]
d5285cf268 docs: update README.md [skip ci] 2021-07-08 20:04:19 +00:00
Eugene Pankov
3db98aa421 expose more plugin loader apis 2021-07-08 22:03:41 +02:00
Eugene Pankov
47dba5b52c avoid including all of the util-decorators in the bundle 2021-07-08 22:03:25 +02:00
Eugene Pankov
72874a1e84 Update HACKING.md 2021-07-08 22:03:03 +02:00
Eugene Pankov
fc1deb67e8 Merge branch 'master' of github.com:Eugeny/terminus 2021-07-08 22:01:43 +02:00
Eugeny
d0bb3c731c Merge pull request #4168 from cypherbits/master 2021-07-08 22:01:34 +02:00
cypherbits
13e54a46d7 add Ubuntu 20.04 compile requeriments 2021-07-08 18:46:44 +02:00
Eugene Pankov
55a975bc8b Merge branch 'master' of github.com:Eugeny/terminus 2021-07-08 16:09:09 +02:00
Eugene Pankov
a62752efec bumped package versions 2021-07-08 16:07:45 +02:00
dependabot[bot]
4e97ce5117 Bump @sentry/cli from 1.64.2 to 1.67.1
Bumps [@sentry/cli](https://github.com/getsentry/sentry-cli) from 1.64.2 to 1.67.1.
- [Release notes](https://github.com/getsentry/sentry-cli/releases)
- [Changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-cli/compare/1.64.2...1.67.1)

---
updated-dependencies:
- dependency-name: "@sentry/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-08 11:36:24 +00:00
Eugeny
19a5f2dc2d Merge pull request #4161 from Eugeny/dependabot/npm_and_yarn/tabby-ssh/types/node-16.0.1
Bump @types/node from 16.0.0 to 16.0.1 in /tabby-ssh
2021-07-08 13:32:41 +02:00
Eugeny
52433afd13 Merge pull request #4159 from Eugeny/dependabot/npm_and_yarn/types/node-16.0.1
Bump @types/node from 16.0.0 to 16.0.1
2021-07-08 13:32:26 +02:00
Eugeny
e1d9f50426 Merge pull request #4158 from Eugeny/dependabot/npm_and_yarn/sentry/electron-2.5.1
Bump @sentry/electron from 2.5.0 to 2.5.1
2021-07-08 13:32:19 +02:00
Eugeny
5837c61ac4 Merge pull request #4157 from Eugeny/dependabot/npm_and_yarn/app/types/node-16.0.1
Bump @types/node from 16.0.0 to 16.0.1 in /app
2021-07-08 13:32:11 +02:00
dependabot[bot]
8c03e5b1aa Bump @types/node from 16.0.0 to 16.0.1 in /tabby-ssh
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.0.0 to 16.0.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-08 04:24:47 +00:00
dependabot[bot]
66074e3eb6 Bump @types/node from 16.0.0 to 16.0.1
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.0.0 to 16.0.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-08 04:04:14 +00:00
dependabot[bot]
221746f3e7 Bump @sentry/electron from 2.5.0 to 2.5.1
Bumps [@sentry/electron](https://github.com/getsentry/sentry-electron) from 2.5.0 to 2.5.1.
- [Release notes](https://github.com/getsentry/sentry-electron/releases)
- [Changelog](https://github.com/getsentry/sentry-electron/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-electron/compare/2.5.0...2.5.1)

---
updated-dependencies:
- dependency-name: "@sentry/electron"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-08 04:03:47 +00:00
dependabot[bot]
2c59e30c39 Bump @types/node from 16.0.0 to 16.0.1 in /app
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.0.0 to 16.0.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-08 04:01:31 +00:00
161 changed files with 7116 additions and 4888 deletions

View File

@@ -415,6 +415,24 @@
"contributions": [ "contributions": [
"doc" "doc"
] ]
},
{
"login": "cypherbits",
"name": "cypherbits",
"avatar_url": "https://avatars.githubusercontent.com/u/10424900?v=4",
"profile": "https://github.com/cypherbits",
"contributions": [
"doc"
]
},
{
"login": "KingMob",
"name": "Matthew Davidson",
"avatar_url": "https://avatars.githubusercontent.com/u/946421?v=4",
"profile": "https://modulolotus.net",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -15,8 +15,8 @@ yarn
``` ```
``` ```
# Linux (Debian here as an example) # Linux (Debian/Ubuntu here as an example)
sudo apt install libfontconfig-dev libsecret-1-dev bsdtar libnss3 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm1 sudo apt install libfontconfig-dev libsecret-1-dev libarchive-tools libnss3 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm1 cmake
yarn yarn
./scripts/build-native.js ./scripts/build-native.js
``` ```

View File

@@ -185,6 +185,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<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/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://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://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>
</tr> </tr>
</table> </table>

View File

@@ -117,7 +117,7 @@ export class Window {
}) })
this.window.on('blur', () => { this.window.on('blur', () => {
if (this.configStore.appearance?.dock !== 'off' && this.configStore.appearance?.dockHideOnBlur) { if ((this.configStore.appearance?.dock ?? 'off') !== 'off' && this.configStore.appearance?.dockHideOnBlur) {
this.hide() this.hide()
} }
}) })

View File

@@ -35,12 +35,12 @@
"macos-native-processlist": "^2.0.0", "macos-native-processlist": "^2.0.0",
"serialport": "^9.2.0", "serialport": "^9.2.0",
"windows-blurbehind": "^1.0.1", "windows-blurbehind": "^1.0.1",
"windows-native-registry": "^3.0.0", "windows-native-registry": "^3.1.0",
"windows-process-tree": "^0.3.0" "windows-process-tree": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/mz": "2.7.4", "@types/mz": "2.7.4",
"@types/node": "16.0.0", "@types/node": "16.0.1",
"ngx-filesize": "^2.0.16", "ngx-filesize": "^2.0.16",
"node-abi": "^2.30.0" "node-abi": "^2.30.0"
}, },

View File

@@ -115,7 +115,8 @@ ngb-typeahead-window {
.hover-reveal-parent:hover &, .hover-reveal-parent:hover &,
*:hover > &, *:hover > &,
&:hover { &:hover,
&.show {
opacity: 1; opacity: 1;
} }
} }
@@ -162,3 +163,27 @@ ngb-typeahead-window {
.list-group-item > button { .list-group-item > button {
margin: -7px 0; margin: -7px 0;
} }
// Windows high contrast mode
@media screen and (forced-colors: active) {
.custom-switch .custom-control-label::before {
background: buttonface;
}
.custom-switch .custom-control-label::after {
background: buttontext;
}
.custom-switch .custom-control-input:checked ~ .custom-control-label::before {
background: activetext;
}
.custom-switch .custom-control-input:checked ~ .custom-control-label::after {
background: canvas;
}
color-scheme-preview, terminaltab > .content {
forced-color-adjust: none;
}
}

View File

@@ -94,10 +94,10 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/node@*", "@types/node@16.0.0": "@types/node@*", "@types/node@16.0.1":
version "16.0.0" version "16.0.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.0.tgz#067a6c49dc7a5c2412a505628e26902ae967bf6f" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.1.tgz#70cedfda26af7a2ca073fdcc9beb2fff4aa693f8"
integrity sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg== integrity sha512-hBOx4SUlEPKwRi6PrXuTGw1z6lz0fjsibcWCM378YxsSu/6+C30L6CR49zIBKHiwNWCYIcOLjg4OHKZaFeLAug==
JSONStream@^1.3.4, JSONStream@^1.3.5: JSONStream@^1.3.4, JSONStream@^1.3.5:
version "1.3.5" version "1.3.5"
@@ -3586,12 +3586,12 @@ windows-blurbehind@^1.0.1:
resolved "https://registry.yarnpkg.com/windows-blurbehind/-/windows-blurbehind-1.0.1.tgz#ff098713873304e38330b2c54cc41bb369b587b9" resolved "https://registry.yarnpkg.com/windows-blurbehind/-/windows-blurbehind-1.0.1.tgz#ff098713873304e38330b2c54cc41bb369b587b9"
integrity sha512-1HzHfCiM1ayrbACJu5qE9zELV24uX/tINT6kxaZwLY3rtQAoeav6x9z7LFHWoLaGDN/sYbnK+9Vk0cz7fsk5HQ== integrity sha512-1HzHfCiM1ayrbACJu5qE9zELV24uX/tINT6kxaZwLY3rtQAoeav6x9z7LFHWoLaGDN/sYbnK+9Vk0cz7fsk5HQ==
windows-native-registry@^3.0.0: windows-native-registry@^3.1.0:
version "3.0.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/windows-native-registry/-/windows-native-registry-3.0.0.tgz#82e715df7a59d5054c768547d81e0bfc81a59d2e" resolved "https://registry.yarnpkg.com/windows-native-registry/-/windows-native-registry-3.1.0.tgz#909ef3254519fdec57d2f149ac59a2c9dc84419a"
integrity sha512-Mz/9a23UivwPc23DsTOL/ZCp/XXogT+6h/khk1psOfDDusXqpomBdxNdsBBE/BvIgOExjGom0XPOfEPiDnHy7A== integrity sha512-WrDysn2V7dH+EYE6cS2RF+7r2P+M0pOYWtU8iBrjV2HaGkCLlUdGUWzOdzT0JPdWwz0BkVu3IOae2xmBajQqBA==
dependencies: dependencies:
node-addon-api "^3.0.0" node-addon-api "^3.1.0"
windows-process-tree@^0.3.0: windows-process-tree@^0.3.0:
version "0.3.0" version "0.3.0"

File diff suppressed because it is too large Load Diff

View File

@@ -9,24 +9,26 @@
"@angular/platform-browser-dynamic": "^12.0.0", "@angular/platform-browser-dynamic": "^12.0.0",
"@fortawesome/fontawesome-free": "^5.15.3", "@fortawesome/fontawesome-free": "^5.15.3",
"@ng-bootstrap/ng-bootstrap": "^10.0.0", "@ng-bootstrap/ng-bootstrap": "^10.0.0",
"@sentry/cli": "^1.64.2", "@sentry/cli": "^1.67.1",
"@sentry/electron": "^2.5.0", "@sentry/electron": "^2.5.1",
"@tabby-gang/to-string-loader": "^1.1.7-beta.2", "@tabby-gang/to-string-loader": "^1.1.7-beta.2",
"@types/electron-config": "^3.2.2", "@types/electron-config": "^3.2.2",
"@types/electron-debug": "^2.1.0", "@types/electron-debug": "^2.1.0",
"@types/fs-extra": "^9.0.12", "@types/fs-extra": "^9.0.12",
"@types/js-yaml": "^4.0.2", "@types/js-yaml": "^4.0.2",
"@types/node": "16.0.0", "@types/node": "16.0.1",
"@types/sortablejs": "^1.10.7",
"@types/webpack-env": "^1.16.2", "@types/webpack-env": "^1.16.2",
"@typescript-eslint/eslint-plugin": "^4.28.2", "@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.28.2", "@typescript-eslint/parser": "^4.28.3",
"apply-loader": "2.0.0", "apply-loader": "2.0.0",
"axios": "^0.21.1",
"clone-deep": "^4.0.1", "clone-deep": "^4.0.1",
"compare-versions": "^3.6.0", "compare-versions": "^3.6.0",
"core-js": "^3.15.2", "core-js": "^3.15.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"css-loader": "5.2.6", "css-loader": "5.2.6",
"electron": "13.1.6", "electron": "13.1.7",
"electron-builder": "22.10.5", "electron-builder": "22.10.5",
"electron-download": "^4.1.1", "electron-download": "^4.1.1",
"electron-installer-snap": "^5.1.0", "electron-installer-snap": "^5.1.0",
@@ -40,6 +42,7 @@
"json-loader": "0.5.7", "json-loader": "0.5.7",
"lru-cache": "^6.0.0", "lru-cache": "^6.0.0",
"macos-release": "^2.5.0", "macos-release": "^2.5.0",
"ngx-sortablejs": "^11.1.0",
"ngx-toastr": "^14.0.0", "ngx-toastr": "^14.0.0",
"node-abi": "^2.30.0", "node-abi": "^2.30.0",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
@@ -53,16 +56,19 @@
"pug-static-loader": "2.0.0", "pug-static-loader": "2.0.0",
"raw-loader": "4.0.2", "raw-loader": "4.0.2",
"sass-loader": "^12.1.0", "sass-loader": "^12.1.0",
"shell-quote": "^1.7.2",
"shelljs": "0.8.4", "shelljs": "0.8.4",
"slugify": "^1.5.3", "slugify": "^1.5.3",
"sortablejs": "^1.14.0",
"source-code-pro": "^2.38.0", "source-code-pro": "^2.38.0",
"source-map-loader": "^3.0.0", "source-map-loader": "^3.0.0",
"source-sans-pro": "3.6.0", "source-sans-pro": "3.6.0",
"style-loader": "^3.0.0", "ssh2": "^1.1.0",
"style-loader": "^3.1.0",
"svg-inline-loader": "^0.8.2", "svg-inline-loader": "^0.8.2",
"ts-loader": "^9.2.3", "ts-loader": "^9.2.3",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"typedoc": "^0.21.2", "typedoc": "^0.21.4",
"typescript": "^4.3.5", "typescript": "^4.3.5",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"val-loader": "4.0.0", "val-loader": "4.0.0",

View File

@@ -1,13 +1,13 @@
diff --git a/node_modules/app-builder-lib/out/appInfo.js b/node_modules/app-builder-lib/out/appInfo.js diff --git a/node_modules/app-builder-lib/out/appInfo.js b/node_modules/app-builder-lib/out/appInfo.js
index 25a159e..d8a0262 100644 index 25a159e..bfe0590 100644
--- a/node_modules/app-builder-lib/out/appInfo.js --- a/node_modules/app-builder-lib/out/appInfo.js
+++ b/node_modules/app-builder-lib/out/appInfo.js +++ b/node_modules/app-builder-lib/out/appInfo.js
@@ -165,7 +165,7 @@ class AppInfo { @@ -165,7 +165,7 @@ class AppInfo {
get linuxPackageName() { get linuxPackageName() {
const name = this.name; // https://github.com/electron-userland/electron-builder/issues/2963 const name = this.name; // https://github.com/electron-userland/electron-builder/issues/2963
- return name.startsWith("@") ? this.sanitizedProductName : name; - return name.startsWith("@") ? this.sanitizedProductName : name;
+ return 'tabby-terminal' + return 'tabby-terminal';
} }
get sanitizedName() { get sanitizedName() {

15
patches/ssh2+1.1.0.patch Normal file
View File

@@ -0,0 +1,15 @@
diff --git a/node_modules/ssh2/lib/protocol/Protocol.js b/node_modules/ssh2/lib/protocol/Protocol.js
index b4d1ee0..1e3ac66 100644
--- a/node_modules/ssh2/lib/protocol/Protocol.js
+++ b/node_modules/ssh2/lib/protocol/Protocol.js
@@ -254,8 +254,8 @@ class Protocol {
);
if (greeting)
this._onWrite(greeting);
- this._onWrite(this._identRaw);
- this._onWrite(CRLF);
+ this._onWrite(Buffer.concat([this._identRaw, CRLF]));
+ // this._onWrite(CRLF);
});
}
_destruct(reason) {

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env node #!/usr/bin/env node
const sh = require('shelljs')
const vars = require('./vars') const vars = require('./vars')
const log = require('npmlog') const log = require('npmlog')
const webpack = require('webpack') const webpack = require('webpack')
@@ -8,8 +7,7 @@ const { promisify } = require('util')
const configs = [ const configs = [
'../app/webpack.main.config.js', '../app/webpack.main.config.js',
'../app/webpack.config.js', '../app/webpack.config.js',
'../web/webpack.config.js', ...vars.allPackages.map(x => `../${x}/webpack.config.js`),
...vars.builtinPlugins.map(x => `../${x}/webpack.config.js`),
] ]
;(async () => { ;(async () => {

View File

@@ -4,8 +4,11 @@ const path = require('path')
const vars = require('./vars') const vars = require('./vars')
const log = require('npmlog') const log = require('npmlog')
const localBinPath = path.resolve(__dirname, '../node_modules/.bin'); const localBinPath = path.resolve(__dirname, '../node_modules/.bin')
const npx = `${localBinPath}/npx`; const npx = `${localBinPath}/npx`
log.info('patch')
sh.exec(`${npx} patch-package`)
log.info('deps', 'app') log.info('deps', 'app')
@@ -18,16 +21,16 @@ sh.exec(`${npx} yarn install --force`)
sh.cd('..') sh.cd('..')
vars.builtinPlugins.forEach(plugin => { vars.builtinPlugins.forEach(plugin => {
log.info('deps', plugin) log.info('deps', plugin)
sh.cd(plugin) sh.cd(plugin)
sh.exec(`${npx} yarn install --force`) sh.exec(`${npx} yarn install --force`)
sh.cd('..') sh.cd('..')
}) })
if (['darwin', 'linux'].includes(process.platform)) { if (['darwin', 'linux'].includes(process.platform)) {
sh.cd('node_modules') sh.cd('node_modules')
for (let x of vars.builtinPlugins) { for (let x of vars.builtinPlugins) {
sh.ln('-fs', '../' + x, x) sh.ln('-fs', '../' + x, x)
} }
sh.cd('..') sh.cd('..')
} }

View File

@@ -3,7 +3,7 @@ const sh = require('shelljs')
const vars = require('./vars') const vars = require('./vars')
const log = require('npmlog') const log = require('npmlog')
;[...vars.builtinPlugins, 'web'].forEach(plugin => { vars.allPackages.forEach(plugin => {
log.info('bump', plugin) log.info('bump', plugin)
sh.cd(plugin) sh.cd(plugin)
sh.exec('npm --no-git-tag-version version ' + vars.version) sh.exec('npm --no-git-tag-version version ' + vars.version)

View File

@@ -10,7 +10,7 @@ exports.version = exports.version.substring(1).trim()
exports.version = exports.version.replace('-', '-c') exports.version = exports.version.replace('-', '-c')
if (exports.version.includes('-c')) { if (exports.version.includes('-c')) {
exports.version = semver.inc(exports.version, 'prepatch').replace('-0', '-nightly.0') exports.version = semver.inc(exports.version, 'prepatch').replace('-0', `-nightly.${process.env.REV ?? 0}`)
} }
exports.builtinPlugins = [ exports.builtinPlugins = [
@@ -26,6 +26,13 @@ exports.builtinPlugins = [
'tabby-serial', 'tabby-serial',
'tabby-telnet', 'tabby-telnet',
] ]
exports.allPackages = [
...exports.builtinPlugins,
'web',
'tabby-web-demo',
]
exports.bundledModules = [ exports.bundledModules = [
'@angular', '@angular',
'@ng-bootstrap', '@ng-bootstrap',

View File

@@ -1 +0,0 @@
dist

View File

@@ -1,6 +1,6 @@
{ {
"name": "tabby-community-color-schemes", "name": "tabby-community-color-schemes",
"version": "1.0.145-nightly.0", "version": "1.0.148-nightly.2",
"description": "Community color schemes for Tabby", "description": "Community color schemes for Tabby",
"keywords": [ "keywords": [
"tabby-builtin-plugin" "tabby-builtin-plugin"

View File

@@ -1 +0,0 @@
dist

View File

@@ -1,6 +1,6 @@
{ {
"name": "tabby-core", "name": "tabby-core",
"version": "1.0.145-nightly.0", "version": "1.0.148-nightly.2",
"description": "Tabby core", "description": "Tabby core",
"keywords": [ "keywords": [
"tabby-builtin-plugin" "tabby-builtin-plugin"

View File

@@ -16,11 +16,11 @@ export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
export { HostWindowService } from './hostWindow' export { HostWindowService } from './hostWindow'
export { HostAppService, Platform } from './hostApp' export { HostAppService, Platform } from './hostApp'
export { FileProvider } from './fileProvider' export { FileProvider } from './fileProvider'
export { ProfileProvider, Profile, ProfileSettingsComponent } from './profileProvider' export { ProfileProvider, Profile, PartialProfile, ProfileSettingsComponent } from './profileProvider'
export { PromptModalComponent } from '../components/promptModal.component' export { PromptModalComponent } from '../components/promptModal.component'
export { AppService } from '../services/app.service' export { AppService } from '../services/app.service'
export { ConfigService } from '../services/config.service' export { ConfigService, configMerge, ConfigProxy } from '../services/config.service'
export { DockingService, Screen } from '../services/docking.service' export { DockingService, Screen } from '../services/docking.service'
export { Logger, ConsoleLogger, LogService } from '../services/log.service' export { Logger, ConsoleLogger, LogService } from '../services/log.service'
export { HomeBaseService } from '../services/homeBase.service' export { HomeBaseService } from '../services/homeBase.service'
@@ -31,6 +31,6 @@ export { ProfilesService } from '../services/profiles.service'
export { SelectorService } from '../services/selector.service' export { SelectorService } from '../services/selector.service'
export { TabsService, NewTabParameters, TabComponentType } from '../services/tabs.service' export { TabsService, NewTabParameters, TabComponentType } from '../services/tabs.service'
export { UpdaterService } from '../services/updater.service' export { UpdaterService } from '../services/updater.service'
export { VaultService, Vault, VaultSecret, VAULT_SECRET_TYPE_FILE } from '../services/vault.service' export { VaultService, Vault, VaultSecret, VaultFileSecret, VAULT_SECRET_TYPE_FILE } from '../services/vault.service'
export { FileProvidersService } from '../services/fileProviders.service' export { FileProvidersService } from '../services/fileProviders.service'
export * from '../utils' export * from '../utils'

View File

@@ -21,6 +21,7 @@ export interface MessageBoxResult {
export abstract class FileTransfer { export abstract class FileTransfer {
abstract getName (): string abstract getName (): string
abstract getMode (): number
abstract getSize (): number abstract getSize (): number
abstract close (): void abstract close (): void
@@ -95,7 +96,7 @@ export abstract class PlatformService {
abstract loadConfig (): Promise<string> abstract loadConfig (): Promise<string>
abstract saveConfig (content: string): Promise<void> abstract saveConfig (content: string): Promise<void>
abstract startDownload (name: string, size: number): Promise<FileDownload|null> abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null>
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]> abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
startUploadFromDragEvent (event: DragEvent, multiple = false): FileUpload[] { startUploadFromDragEvent (event: DragEvent, multiple = false): FileUpload[] {
@@ -188,6 +189,10 @@ export class HTMLFileUpload extends FileUpload {
return this.file.name return this.file.name
} }
getMode (): number {
return 0o644
}
getSize (): number { getSize (): number {
return this.file.size return this.file.size
} }

View File

@@ -1,44 +1,56 @@
/* eslint-disable @typescript-eslint/no-type-alias */
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-empty-function */
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { NewTabParameters } from '../services/tabs.service' import { NewTabParameters } from '../services/tabs.service'
export interface Profile { export interface Profile {
id?: string id: string
type: string type: string
name: string name: string
group?: string group?: string
options?: Record<string, any> options: any
icon?: string icon?: string
color?: string color?: string
disableDynamicTitle?: boolean disableDynamicTitle: boolean
weight?: number weight: number
isBuiltin?: boolean isBuiltin: boolean
isTemplate?: boolean isTemplate: boolean
} }
export interface ProfileSettingsComponent { export type PartialProfile<T extends Profile> = Omit<Omit<Omit<{
profile: Profile [K in keyof T]?: T[K]
}, 'options'>, 'type'>, 'name'> & {
type: string
name: string
options?: {
[K in keyof T['options']]?: T['options'][K]
}
}
export interface ProfileSettingsComponent<P extends Profile> {
profile: P
save?: () => void save?: () => void
} }
export abstract class ProfileProvider { export abstract class ProfileProvider<P extends Profile> {
id: string id: string
name: string name: string
supportsQuickConnect = false supportsQuickConnect = false
settingsComponent: new (...args: any[]) => ProfileSettingsComponent settingsComponent?: new (...args: any[]) => ProfileSettingsComponent<P>
configDefaults = {}
abstract getBuiltinProfiles (): Promise<Profile[]> abstract getBuiltinProfiles (): Promise<PartialProfile<P>[]>
abstract getNewTabParameters (profile: Profile): Promise<NewTabParameters<BaseTabComponent>> abstract getNewTabParameters (profile: PartialProfile<P>): Promise<NewTabParameters<BaseTabComponent>>
abstract getDescription (profile: Profile): string abstract getDescription (profile: PartialProfile<P>): string
quickConnect (query: string): Profile|null { quickConnect (query: string): PartialProfile<P>|null {
return null return null
} }
deleteProfile (profile: Profile): void { } deleteProfile (profile: P): void { }
} }

View File

@@ -2,26 +2,19 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider' import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider'
import { SelectorService } from './services/selector.service'
import { HostAppService, Platform } from './api/hostApp' import { HostAppService, Platform } from './api/hostApp'
import { Profile } from './api/profileProvider' import { PartialProfile, Profile } from './api/profileProvider'
import { ConfigService } from './services/config.service' import { ConfigService } from './services/config.service'
import { SelectorOption } from './api/selector'
import { HotkeysService } from './services/hotkeys.service' import { HotkeysService } from './services/hotkeys.service'
import { ProfilesService } from './services/profiles.service' import { ProfilesService } from './services/profiles.service'
import { AppService } from './services/app.service'
import { NotificationsService } from './services/notifications.service'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
export class ButtonProvider extends ToolbarButtonProvider { export class ButtonProvider extends ToolbarButtonProvider {
constructor ( constructor (
private selector: SelectorService,
private app: AppService,
private hostApp: HostAppService, private hostApp: HostAppService,
private profilesServices: ProfilesService, private profilesService: ProfilesService,
private config: ConfigService, private config: ConfigService,
private notifications: NotificationsService,
hotkeys: HotkeysService, hotkeys: HotkeysService,
) { ) {
super() super()
@@ -33,77 +26,17 @@ export class ButtonProvider extends ToolbarButtonProvider {
} }
async activate () { async activate () {
const recentProfiles: Profile[] = this.config.store.recentProfiles const profile = await this.profilesService.showProfileSelector()
if (profile) {
const getProfileOptions = (profile): SelectorOption<void> => { this.launchProfile(profile)
const result: SelectorOption<void> = this.profilesServices.selectorOptionForProfile(profile)
if (recentProfiles.includes(profile)) {
result.icon = 'fas fa-history'
}
result.callback = () => this.launchProfile(profile)
return result
} }
let options = recentProfiles.map(getProfileOptions)
if (recentProfiles.length) {
options.push({
name: 'Clear recent connections',
icon: 'fas fa-eraser',
callback: () => {
this.config.store.recentProfiles = []
this.config.save()
},
})
}
let profiles = await this.profilesServices.getProfiles()
if (!this.config.store.terminal.showBuiltinProfiles) {
profiles = profiles.filter(x => !x.isBuiltin)
}
profiles = profiles.filter(x => !x.isTemplate)
options = [...options, ...profiles.map(getProfileOptions)]
try {
const { SettingsTabComponent } = window['nodeRequire']('tabby-settings')
options.push({
name: 'Manage profiles',
icon: 'fas fa-window-restore',
callback: () => this.app.openNewTabRaw({
type: SettingsTabComponent,
inputs: { activeTab: 'profiles' },
}),
})
} catch { }
if (this.profilesServices.getProviders().some(x => x.supportsQuickConnect)) {
options.push({
name: 'Quick connect',
freeInputPattern: 'Connect to "%s"...',
icon: 'fas fa-arrow-right',
callback: query => this.quickConnect(query),
})
}
await this.selector.show('Select profile', options)
} }
quickConnect (query: string) { async launchProfile (profile: PartialProfile<Profile>) {
for (const provider of this.profilesServices.getProviders()) { await this.profilesService.openNewTabForProfile(profile)
const profile = provider.quickConnect(query)
if (profile) {
this.launchProfile(profile)
return
}
}
this.notifications.error(`Could not parse "${query}"`)
}
async launchProfile (profile: Profile) { let recentProfiles = this.config.store.recentProfiles
await this.profilesServices.openNewTabForProfile(profile) recentProfiles = recentProfiles.filter(x => x.group !== profile.group || x.name !== profile.name)
const recentProfiles = this.config.store.recentProfiles
recentProfiles.unshift(profile) recentProfiles.unshift(profile)
if (recentProfiles.length > 5) { if (recentProfiles.length > 5) {
recentProfiles.pop() recentProfiles.pop()

View File

@@ -64,7 +64,7 @@ export class AppRootComponent {
activeTransfersDropdownOpen = false activeTransfersDropdownOpen = false
private logger: Logger private logger: Logger
private constructor ( constructor (
private hotkeys: HotkeysService, private hotkeys: HotkeysService,
private updater: UpdaterService, private updater: UpdaterService,
public hostWindow: HostWindowService, public hostWindow: HostWindowService,

View File

@@ -1,19 +1,19 @@
.modal-body .modal-body
input.form-control( input.form-control(
[type]='password ? "password" : "text"', [type]='password ? "password" : "text"',
autofocus, autofocus,
[(ngModel)]='value', [(ngModel)]='value',
#input, #input,
[placeholder]='prompt', [placeholder]='prompt',
(keyup.enter)='ok()', (keyup.enter)='ok()',
(keyup.esc)='cancel()', (keyup.esc)='cancel()',
) )
.d-flex.align-items-start.mt-2 .d-flex.align-items-start.mt-2
checkbox( checkbox(
*ngIf='showRememberCheckbox', *ngIf='showRememberCheckbox',
[(ngModel)]='remember', [(ngModel)]='remember',
text='Remember' text='Remember'
) )
button.btn.btn-primary.ml-auto( button.btn.btn-primary.ml-auto(
(click)='ok()', (click)='ok()',
) Enter ) OK

View File

@@ -2,5 +2,5 @@
input.form-control(type='text', #input, [(ngModel)]='value', (keyup.enter)='save()', autofocus) input.form-control(type='text', #input, [(ngModel)]='value', (keyup.enter)='save()', autofocus)
.modal-footer .modal-footer
button.btn.btn-outline-primary((click)='save()') Save button.btn.btn-primary((click)='save()') Save
button.btn.btn-outline-secondary((click)='close()') Cancel button.btn.btn-secondary((click)='close()') Cancel

View File

@@ -4,4 +4,4 @@
pre {{error}} pre {{error}}
.modal-footer .modal-footer
button.btn.btn-outline-primary((click)='close()') Close button.btn.btn-primary((click)='close()') Close

View File

@@ -369,12 +369,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
await this.initialized$.toPromise() await this.initialized$.toPromise()
this.attachTabView(tab) this.attachTabView(tab)
this.onAfterTabAdded(tab)
setImmediate(() => {
this.layout()
this.tabAdded.next(tab)
this.focus(tab)
})
} }
removeTab (tab: BaseTabComponent): void { removeTab (tab: BaseTabComponent): void {
@@ -399,6 +394,21 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
} }
} }
replaceTab (tab: BaseTabComponent, newTab: BaseTabComponent): void {
const parent = this.getParentOf(tab)
if (!parent) {
return
}
const position = parent.children.indexOf(tab)
parent.children[position] = newTab
this.detachTabView(tab)
this.attachTabView(newTab)
tab.parent = null
newTab.parent = this
this.recoveryStateChangedHint.next()
this.onAfterTabAdded(newTab)
}
/** /**
* Moves focus in the given direction * Moves focus in the given direction
*/ */
@@ -539,6 +549,14 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
} }
} }
private onAfterTabAdded (tab: BaseTabComponent) {
setImmediate(() => {
this.layout()
this.tabAdded.next(tab)
this.focus(tab)
})
}
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) { private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
const size = root.orientation === 'v' ? h : w const size = root.orientation === 'v' ? h : w
const sizes = root.ratios.map(ratio => ratio * size) const sizes = root.ratios.map(ratio => ratio * size)
@@ -618,7 +636,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
} }
/** @hidden */ /** @hidden */
@Injectable() @Injectable({ providedIn: 'root' })
export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabComponent> { export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabComponent> {
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> { async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
return recoveryToken.type === 'app:split-tab' return recoveryToken.type === 'app:split-tab'

View File

@@ -13,7 +13,7 @@ import { ToolbarButton, ToolbarButtonProvider } from '../api'
export class StartPageComponent { export class StartPageComponent {
version: string version: string
private constructor ( constructor (
private config: ConfigService, private config: ConfigService,
private domSanitizer: DomSanitizer, private domSanitizer: DomSanitizer,
public homeBase: HomeBaseService, public homeBase: HomeBaseService,

View File

@@ -6,9 +6,6 @@ import { BaseTabComponent } from '../components/baseTab.component'
@Component({ @Component({
selector: 'tab-body', selector: 'tab-body',
template: ` template: `
<!--perfect-scrollbar [config]="{ suppressScrollX: true }" *ngIf="scrollable">
<ng-template #scrollablePlaceholder></ng-template>
</perfect-scrollbar-->
<ng-template #placeholder></ng-template> <ng-template #placeholder></ng-template>
`, `,
styles: [ styles: [

View File

@@ -31,7 +31,7 @@ export class TabHeaderComponent extends BaseComponent {
@Input() progress: number|null @Input() progress: number|null
@ViewChild('handle') handle?: ElementRef @ViewChild('handle') handle?: ElementRef
private constructor ( constructor (
public app: AppService, public app: AppService,
public config: ConfigService, public config: ConfigService,
private hostApp: HostAppService, private hostApp: HostAppService,

View File

@@ -10,7 +10,7 @@ import { AppService } from '../services/app.service'
styles: [require('./windowControls.component.scss')], styles: [require('./windowControls.component.scss')],
}) })
export class WindowControlsComponent { export class WindowControlsComponent {
private constructor (public hostWindow: HostWindowService, public app: AppService) { } constructor (public hostWindow: HostWindowService, public app: AppService) { }
async closeWindow () { async closeWindow () {
this.app.closeWindow() this.app.closeWindow()

View File

@@ -69,6 +69,8 @@ hotkeys:
pane-maximize: pane-maximize:
- 'Ctrl-Alt-Enter' - 'Ctrl-Alt-Enter'
close-pane: [] close-pane: []
switch-profile:
- 'Ctrl-Alt-T'
profile-selector: profile-selector:
- 'Ctrl-Shift-T' - 'Ctrl-Shift-T'
pluginBlacklist: ['ssh'] pluginBlacklist: ['ssh']

View File

@@ -70,4 +70,6 @@ hotkeys:
- '⌘-Shift-W' - '⌘-Shift-W'
profile-selector: profile-selector:
- '⌘-E' - '⌘-E'
switch-profile:
- '⌘-Shift-E'
pluginBlacklist: ['ssh'] pluginBlacklist: ['ssh']

View File

@@ -70,6 +70,8 @@ hotkeys:
pane-maximize: pane-maximize:
- 'Ctrl-Alt-Enter' - 'Ctrl-Alt-Enter'
close-pane: [] close-pane: []
switch-profile:
- 'Ctrl-Alt-T'
profile-selector: profile-selector:
- 'Ctrl-Shift-T' - 'Ctrl-Shift-T'
pluginBlacklist: [] pluginBlacklist: []

View File

@@ -170,6 +170,10 @@ export class AppHotkeyProvider extends HotkeyProvider {
id: 'pane-nav-next', id: 'pane-nav-next',
name: 'Focus next pane', name: 'Focus next pane',
}, },
{
id: 'switch-profile',
name: 'Switch profile in the active pane',
},
{ {
id: 'close-pane', id: 'close-pane',
name: 'Close focused pane', name: 'Close focused pane',

View File

@@ -6,6 +6,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar' import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
import { NgxFilesizeModule } from 'ngx-filesize' import { NgxFilesizeModule } from 'ngx-filesize'
import { DndModule } from 'ng2-dnd' import { DndModule } from 'ng2-dnd'
import { SortablejsModule } from 'ngx-sortablejs'
import { AppRootComponent } from './components/appRoot.component' import { AppRootComponent } from './components/appRoot.component'
import { CheckboxComponent } from './components/checkbox.component' import { CheckboxComponent } from './components/checkbox.component'
@@ -30,7 +31,7 @@ import { AlwaysVisibleTypeaheadDirective } from './directives/alwaysVisibleTypea
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive' import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
import { DropZoneDirective } from './directives/dropZone.directive' import { DropZoneDirective } from './directives/dropZone.directive'
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ToolbarButtonProvider, ProfilesService } from './api' import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ToolbarButtonProvider, ProfilesService, ProfileProvider } from './api'
import { AppService } from './services/app.service' import { AppService } from './services/app.service'
import { ConfigService } from './services/config.service' import { ConfigService } from './services/config.service'
@@ -40,9 +41,10 @@ import { HotkeysService } from './services/hotkeys.service'
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme' import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
import { CoreConfigProvider } from './config' import { CoreConfigProvider } from './config'
import { AppHotkeyProvider } from './hotkeys' import { AppHotkeyProvider } from './hotkeys'
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu } from './tabContextMenu' import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu, ProfilesContextMenu } from './tabContextMenu'
import { LastCLIHandler, ProfileCLIHandler } from './cli' import { LastCLIHandler, ProfileCLIHandler } from './cli'
import { ButtonProvider } from './buttonProvider' import { ButtonProvider } from './buttonProvider'
import { SplitLayoutProfilesService } from './profiles'
import 'perfect-scrollbar/css/perfect-scrollbar.css' import 'perfect-scrollbar/css/perfect-scrollbar.css'
import 'ng2-dnd/bundles/style.css' import 'ng2-dnd/bundles/style.css'
@@ -56,12 +58,14 @@ const PROVIDERS = [
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true }, { provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true }, { provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true }, { provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true }, { provide: TabContextMenuItemProvider, useClass: ProfilesContextMenu, multi: true },
{ provide: TabRecoveryProvider, useExisting: SplitTabRecoveryProvider, multi: true },
{ provide: CLIHandler, useClass: ProfileCLIHandler, multi: true }, { provide: CLIHandler, useClass: ProfileCLIHandler, multi: true },
{ provide: CLIHandler, useClass: LastCLIHandler, multi: true }, { provide: CLIHandler, useClass: LastCLIHandler, multi: true },
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } }, { provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
{ provide: FileProvider, useClass: VaultFileProvider, multi: true }, { provide: FileProvider, useClass: VaultFileProvider, multi: true },
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }, { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: ProfileProvider, useExisting: SplitLayoutProfilesService, multi: true },
] ]
/** @hidden */ /** @hidden */
@@ -74,9 +78,10 @@ const PROVIDERS = [
NgxFilesizeModule, NgxFilesizeModule,
PerfectScrollbarModule, PerfectScrollbarModule,
DndModule.forRoot(), DndModule.forRoot(),
SortablejsModule.forRoot({ animation: 150 }),
], ],
declarations: [ declarations: [
AppRootComponent as any, AppRootComponent,
CheckboxComponent, CheckboxComponent,
PromptModalComponent, PromptModalComponent,
StartPageComponent, StartPageComponent,
@@ -115,6 +120,7 @@ const PROVIDERS = [
DropZoneDirective, DropZoneDirective,
FastHtmlBindDirective, FastHtmlBindDirective,
AlwaysVisibleTypeaheadDirective, AlwaysVisibleTypeaheadDirective,
SortablejsModule,
], ],
}) })
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -0,0 +1,57 @@
import slugify from 'slugify'
import { v4 as uuidv4 } from 'uuid'
import { Injectable } from '@angular/core'
import { ConfigService, NewTabParameters, PartialProfile, Profile, ProfileProvider } from './api'
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
export interface SplitLayoutProfileOptions {
recoveryToken: any
}
export interface SplitLayoutProfile extends Profile {
options: SplitLayoutProfileOptions
}
@Injectable({ providedIn: 'root' })
export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfile> {
id = 'split-layout'
name = 'Saved layout'
configDefaults = {
options: {
recoveryToken: null,
},
}
constructor (
private splitTabRecoveryProvider: SplitTabRecoveryProvider,
private config: ConfigService,
) {
super()
}
async getBuiltinProfiles (): Promise<PartialProfile<SplitLayoutProfile>[]> {
return []
}
async getNewTabParameters (profile: SplitLayoutProfile): Promise<NewTabParameters<SplitTabComponent>> {
return this.splitTabRecoveryProvider.recover(profile.options.recoveryToken)
}
getDescription (_: SplitLayoutProfile): string {
return ''
}
async createProfile (tab: SplitTabComponent, name: string): Promise<void> {
const token = await tab.getRecoveryToken()
const profile: PartialProfile<SplitLayoutProfile> = {
id: `${this.id}:custom:${slugify(name)}:${uuidv4()}`,
type: this.id,
name,
options: {
recoveryToken: token,
},
}
this.config.store.profiles.push(profile)
await this.config.save()
}
}

View File

@@ -1,3 +1,5 @@
import deepClone from 'clone-deep'
import deepEqual from 'deep-equal'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import { Observable, Subject, AsyncSubject } from 'rxjs' import { Observable, Subject, AsyncSubject } from 'rxjs'
@@ -8,7 +10,8 @@ import { HostAppService } from '../api/hostApp'
import { Vault, VaultService } from './vault.service' import { Vault, VaultService } from './vault.service'
const deepmerge = require('deepmerge') const deepmerge = require('deepmerge')
const configMerge = (a, b) => deepmerge(a, b, { arrayMerge: (_d, s) => s }) // eslint-disable-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const configMerge = (a, b) => deepmerge(a, b, { arrayMerge: (_d, s) => s }) // eslint-disable-line @typescript-eslint/no-var-requires
const LATEST_VERSION = 1 const LATEST_VERSION = 1
@@ -18,7 +21,7 @@ function isStructuralMember (v) {
} }
function isNonStructuralObjectMember (v): boolean { function isNonStructuralObjectMember (v): boolean {
return v instanceof Object && !(v instanceof Array) && v.__nonStructural return v instanceof Object && (v instanceof Array || v.__nonStructural)
} }
/** @hidden */ /** @hidden */
@@ -46,49 +49,63 @@ export class ConfigProxy {
{ {
enumerable: true, enumerable: true,
configurable: false, configurable: false,
get: () => this.getValue(key), get: () => this.__getValue(key),
set: (value) => { set: (value) => {
this.setValue(key, value) this.__setValue(key, value)
}, },
} }
) )
} }
} }
this.getValue = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method this.__getValue = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
if (real[key] !== undefined) { if (real[key] !== undefined) {
return real[key] return real[key]
} else { } else {
return this.getDefault(key) if (isNonStructuralObjectMember(defaults[key])) {
// The object might be modified outside
real[key] = this.__getDefault(key)
delete real[key].__nonStructural
return real[key]
}
return this.__getDefault(key)
} }
} }
this.getDefault = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method this.__getDefault = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
if (isNonStructuralObjectMember(defaults[key])) { return deepClone(defaults[key])
real[key] = { ...defaults[key] }
delete real[key].__nonStructural
return real[key]
} else {
return defaults[key]
}
} }
this.setValue = (key: string, value: any) => { // eslint-disable-line @typescript-eslint/unbound-method this.__setValue = (key: string, value: any) => { // eslint-disable-line @typescript-eslint/unbound-method
if (value === this.getDefault(key)) { if (deepEqual(value, this.__getDefault(key))) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete real[key] delete real[key]
} else { } else {
real[key] = value real[key] = value
} }
} }
this.__cleanup = () => { // eslint-disable-line @typescript-eslint/unbound-method
// Trigger removal of default values
for (const key in defaults) {
if (isStructuralMember(defaults[key])) {
this[key].__cleanup()
} else {
const v = this.__getValue(key)
this.__setValue(key, v)
}
}
}
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
getValue (_key: string): any { } __getValue (_key: string): any { }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
setValue (_key: string, _value: any) { } __setValue (_key: string, _value: any) { }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
getDefault (_key: string): any { } __getDefault (_key: string): any { }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
__cleanup () { }
} }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@@ -177,6 +194,7 @@ export class ConfigService {
} }
async save (): Promise<void> { async save (): Promise<void> {
this.store.__cleanup()
// Scrub undefined values // Scrub undefined values
let cleanStore = JSON.parse(JSON.stringify(this._store)) let cleanStore = JSON.parse(JSON.stringify(this._store))
cleanStore = await this.maybeEncryptConfig(cleanStore) cleanStore = await this.maybeEncryptConfig(cleanStore)

View File

@@ -3,6 +3,7 @@ import { Observable, Subject } from 'rxjs'
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider' import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
import { stringifyKeySequence, EventData } from './hotkeys.util' import { stringifyKeySequence, EventData } from './hotkeys.util'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import { HostAppService, Platform } from '../api/hostApp'
import { deprecate } from 'util' import { deprecate } from 'util'
export interface PartialHotkeyMatch { export interface PartialHotkeyMatch {
@@ -35,6 +36,7 @@ export class HotkeysService {
private zone: NgZone, private zone: NgZone,
private config: ConfigService, private config: ConfigService,
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[], @Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
hostApp: HostAppService,
) { ) {
const events = ['keydown', 'keyup'] const events = ['keydown', 'keyup']
events.forEach(event => { events.forEach(event => {
@@ -43,6 +45,10 @@ export class HotkeysService {
this.pushKeystroke(event, nativeEvent) this.pushKeystroke(event, nativeEvent)
this.processKeystrokes() this.processKeystrokes()
this.emitKeyEvent(nativeEvent) this.emitKeyEvent(nativeEvent)
if (hostApp.platform === Platform.Web) {
nativeEvent.preventDefault()
nativeEvent.stopPropagation()
}
} }
}) })
}) })
@@ -53,7 +59,7 @@ export class HotkeysService {
// deprecated // deprecated
this.hotkey$.subscribe(h => this.matchedHotkey.emit(h)) this.hotkey$.subscribe(h => this.matchedHotkey.emit(h))
this.matchedHotkey.subscribe = deprecate(s => this.matchedHotkey.subscribe(s), 'matchedHotkey is deprecated, use hotkey$') this.matchedHotkey.subscribe = deprecate(s => this.hotkey$.subscribe(s), 'matchedHotkey is deprecated, use hotkey$')
} }
/** /**
@@ -63,7 +69,7 @@ export class HotkeysService {
* @param nativeEvent event object * @param nativeEvent event object
*/ */
pushKeystroke (name: string, nativeEvent: KeyboardEvent): void { pushKeystroke (name: string, nativeEvent: KeyboardEvent): void {
(nativeEvent as any).event = name nativeEvent['event'] = name
this.currentKeystrokes.push({ this.currentKeystrokes.push({
ctrlKey: nativeEvent.ctrlKey, ctrlKey: nativeEvent.ctrlKey,
metaKey: nativeEvent.metaKey, metaKey: nativeEvent.metaKey,

View File

@@ -1,25 +1,46 @@
import { Injectable, Inject } from '@angular/core' import { Injectable, Inject } from '@angular/core'
import { NewTabParameters } from './tabs.service' import { NewTabParameters } from './tabs.service'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { Profile, ProfileProvider } from '../api/profileProvider' import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider'
import { SelectorOption } from '../api/selector' import { SelectorOption } from '../api/selector'
import { AppService } from './app.service' import { AppService } from './app.service'
import { ConfigService } from './config.service' import { configMerge, ConfigProxy, ConfigService } from './config.service'
import { NotificationsService } from './notifications.service'
import { SelectorService } from './selector.service'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ProfilesService { export class ProfilesService {
private profileDefaults = {
id: '',
type: '',
name: '',
group: '',
options: {},
icon: '',
color: '',
disableDynamicTitle: false,
weight: 0,
isBuiltin: false,
isTemplate: false,
}
constructor ( constructor (
private app: AppService, private app: AppService,
private config: ConfigService, private config: ConfigService,
@Inject(ProfileProvider) private profileProviders: ProfileProvider[], private notifications: NotificationsService,
private selector: SelectorService,
@Inject(ProfileProvider) private profileProviders: ProfileProvider<Profile>[],
) { } ) { }
async openNewTabForProfile (profile: Profile): Promise<BaseTabComponent|null> { async openNewTabForProfile <P extends Profile> (profile: PartialProfile<P>): Promise<BaseTabComponent|null> {
const params = await this.newTabParametersForProfile(profile) const params = await this.newTabParametersForProfile(profile)
if (params) { if (params) {
const tab = this.app.openNewTab(params) const tab = this.app.openNewTab(params)
;(this.app.getParentTab(tab) ?? tab).color = profile.color ?? null ;(this.app.getParentTab(tab) ?? tab).color = profile.color ?? null
tab.setTitle(profile.name)
if (profile.name) {
tab.setTitle(profile.name)
}
if (profile.disableDynamicTitle) { if (profile.disableDynamicTitle) {
tab['enableDynamicTitle'] = false tab['enableDynamicTitle'] = false
} }
@@ -28,36 +49,136 @@ export class ProfilesService {
return null return null
} }
async newTabParametersForProfile (profile: Profile): Promise<NewTabParameters<BaseTabComponent>|null> { async newTabParametersForProfile <P extends Profile> (profile: PartialProfile<P>): Promise<NewTabParameters<BaseTabComponent>|null> {
return this.providerForProfile(profile)?.getNewTabParameters(profile) ?? null const fullProfile = this.getConfigProxyForProfile(profile)
return this.providerForProfile(fullProfile)?.getNewTabParameters(fullProfile) ?? null
} }
getProviders (): ProfileProvider[] { getProviders (): ProfileProvider<Profile>[] {
return [...this.profileProviders] return [...this.profileProviders]
} }
async getProfiles (): Promise<Profile[]> { async getProfiles (): Promise<PartialProfile<Profile>[]> {
const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles())) const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles()))
let list = lists.reduce((a, b) => a.concat(b), []) let list = lists.reduce((a, b) => a.concat(b), [])
list = [ list = [
...this.config.store.profiles ?? [], ...this.config.store.profiles ?? [],
...list, ...list,
] ]
list.sort((a, b) => a.group?.localeCompare(b.group ?? '') ?? -1) const sortKey = p => `${p.group ?? ''} / ${p.name}`
list.sort((a, b) => a.name.localeCompare(b.name)) list.sort((a, b) => sortKey(a).localeCompare(sortKey(b)))
list.sort((a, b) => (a.isBuiltin ? 1 : 0) - (b.isBuiltin ? 1 : 0)) list.sort((a, b) => (a.isBuiltin ? 1 : 0) - (b.isBuiltin ? 1 : 0))
return list return list
} }
providerForProfile (profile: Profile): ProfileProvider|null { providerForProfile <T extends Profile> (profile: PartialProfile<T>): ProfileProvider<T>|null {
return this.profileProviders.find(x => x.id === profile.type) ?? null const provider = this.profileProviders.find(x => x.id === profile.type) ?? null
return provider as unknown as ProfileProvider<T>|null
} }
selectorOptionForProfile <T> (profile: Profile): SelectorOption<T> { getDescription <P extends Profile> (profile: PartialProfile<P>): string|null {
profile = this.getConfigProxyForProfile(profile)
return this.providerForProfile(profile)?.getDescription(profile) ?? null
}
selectorOptionForProfile <P extends Profile, T> (profile: PartialProfile<P>): SelectorOption<T> {
const fullProfile = this.getConfigProxyForProfile(profile)
return { return {
icon: profile.icon, icon: profile.icon,
name: profile.group ? `${profile.group} / ${profile.name}` : profile.name, name: profile.group ? `${fullProfile.group} / ${fullProfile.name}` : fullProfile.name,
description: this.providerForProfile(profile)?.getDescription(profile), description: this.providerForProfile(fullProfile)?.getDescription(fullProfile),
} }
} }
showProfileSelector (): Promise<PartialProfile<Profile>|null> {
return new Promise<PartialProfile<Profile>|null>(async (resolve, reject) => {
try {
const recentProfiles: PartialProfile<Profile>[] = this.config.store.recentProfiles
let options: SelectorOption<void>[] = recentProfiles.map(p => ({
...this.selectorOptionForProfile(p),
icon: 'fas fa-history',
callback: async () => {
if (p.id) {
p = (await this.getProfiles()).find(x => x.id === p.id) ?? p
}
resolve(p)
},
}))
if (recentProfiles.length) {
options.push({
name: 'Clear recent connections',
icon: 'fas fa-eraser',
callback: async () => {
this.config.store.recentProfiles = []
this.config.save()
resolve(null)
},
})
}
let profiles = await this.getProfiles()
if (!this.config.store.terminal.showBuiltinProfiles) {
profiles = profiles.filter(x => !x.isBuiltin)
}
profiles = profiles.filter(x => !x.isTemplate)
options = [...options, ...profiles.map((p): SelectorOption<void> => ({
...this.selectorOptionForProfile(p),
callback: () => resolve(p),
}))]
try {
const { SettingsTabComponent } = window['nodeRequire']('tabby-settings')
options.push({
name: 'Manage profiles',
icon: 'fas fa-window-restore',
callback: () => {
this.app.openNewTabRaw({
type: SettingsTabComponent,
inputs: { activeTab: 'profiles' },
})
resolve(null)
},
})
} catch { }
if (this.getProviders().some(x => x.supportsQuickConnect)) {
options.push({
name: 'Quick connect',
freeInputPattern: 'Connect to "%s"...',
icon: 'fas fa-arrow-right',
callback: query => {
const profile = this.quickConnect(query)
resolve(profile)
},
})
}
await this.selector.show('Select profile or enter an address', options)
} catch (err) {
reject(err)
}
})
}
async quickConnect (query: string): Promise<PartialProfile<Profile>|null> {
for (const provider of this.getProviders()) {
if (provider.supportsQuickConnect) {
const profile = provider.quickConnect(query)
if (profile) {
return profile
}
}
}
this.notifications.error(`Could not parse "${query}"`)
return null
}
getConfigProxyForProfile <T extends Profile> (profile: PartialProfile<T>): T {
const provider = this.providerForProfile(profile)
const defaults = configMerge(this.profileDefaults, provider?.configDefaults ?? {})
return new ConfigProxy(profile, defaults) as unknown as T
}
} }

View File

@@ -30,6 +30,13 @@ export interface VaultSecret {
value: string value: string
} }
export interface VaultFileSecret extends VaultSecret {
key: {
id: string
description: string
}
}
export interface Vault { export interface Vault {
config: any config: any
secrets: VaultSecret[] secrets: VaultSecret[]
@@ -121,6 +128,10 @@ export class VaultService {
return !!_rememberedPassphrase return !!_rememberedPassphrase
} }
forgetPassphrase (): void {
_rememberedPassphrase = null
}
async decrypt (storage: StoredVault, passphrase?: string): Promise<Vault> { async decrypt (storage: StoredVault, passphrase?: string): Promise<Vault> {
if (!passphrase) { if (!passphrase) {
passphrase = await this.getPassphrase() passphrase = await this.getPassphrase()
@@ -128,7 +139,7 @@ export class VaultService {
try { try {
return await wrapPromise(this.zone, decryptVault(storage, passphrase)) return await wrapPromise(this.zone, decryptVault(storage, passphrase))
} catch (e) { } catch (e) {
_rememberedPassphrase = null this.forgetPassphrase()
if (e.toString().includes('BAD_DECRYPT')) { if (e.toString().includes('BAD_DECRYPT')) {
this.notifications.error('Incorrect passphrase') this.notifications.error('Incorrect passphrase')
} }
@@ -193,6 +204,20 @@ export class VaultService {
await this.save(vault) await this.save(vault)
} }
async updateSecret (secret: VaultSecret, update: VaultSecret): Promise<void> {
await this.ready$.toPromise()
const vault = await this.load()
if (!vault) {
return
}
const target = vault.secrets.find(s => s.type === secret.type && this.keyMatches(secret.key, s))
if (!target) {
return
}
Object.assign(target, update)
await this.save(vault)
}
async removeSecret (type: string, key: Record<string, any>): Promise<void> { async removeSecret (type: string, key: Record<string, any>): Promise<void> {
await this.ready$.toPromise() await this.ready$.toPromise()
const vault = await this.load() const vault = await this.load()
@@ -274,7 +299,7 @@ export class VaultFileProvider extends FileProvider {
type: VAULT_SECRET_TYPE_FILE, type: VAULT_SECRET_TYPE_FILE,
key: { key: {
id, id,
description, description: `${description} (${transfer.getName()})`,
}, },
value: (await transfer.readAll()).toString('base64'), value: (await transfer.readAll()).toString('base64'),
}) })

View File

@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { AppService } from './services/app.service' import { AppService } from './services/app.service'
import { BaseTabComponent } from './components/baseTab.component' import { BaseTabComponent } from './components/baseTab.component'
@@ -7,6 +8,11 @@ import { TabHeaderComponent } from './components/tabHeader.component'
import { SplitTabComponent, SplitDirection } from './components/splitTab.component' import { SplitTabComponent, SplitDirection } from './components/splitTab.component'
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider' import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
import { MenuItemOptions } from './api/menu' import { MenuItemOptions } from './api/menu'
import { ProfilesService } from './services/profiles.service'
import { TabsService } from './services/tabs.service'
import { HotkeysService } from './services/hotkeys.service'
import { PromptModalComponent } from './components/promptModal.component'
import { SplitLayoutProfilesService } from './profiles'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -100,6 +106,8 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
constructor ( constructor (
private app: AppService, private app: AppService,
private ngbModal: NgbModal,
private splitLayoutProfilesService: SplitLayoutProfilesService,
) { ) {
super() super()
} }
@@ -130,6 +138,21 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
})) as MenuItemOptions[], })) as MenuItemOptions[],
}, },
] ]
if (tab instanceof SplitTabComponent && tab.getAllTabs().length > 1) {
items.push({
label: 'Save layout as profile',
click: async () => {
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = 'Profile name'
const name = (await modal.result)?.value
if (!name) {
return
}
this.splitLayoutProfilesService.createProfile(tab, name)
},
})
}
} }
return items return items
} }
@@ -203,3 +226,65 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
return items return items
} }
} }
/** @hidden */
@Injectable()
export class ProfilesContextMenu extends TabContextMenuItemProvider {
weight = 10
constructor (
private profilesService: ProfilesService,
private tabsService: TabsService,
private app: AppService,
hotkeys: HotkeysService,
) {
super()
hotkeys.hotkey$.subscribe(hotkey => {
if (hotkey === 'switch-profile') {
let tab = this.app.activeTab
if (tab instanceof SplitTabComponent) {
tab = tab.getFocusedTab()
if (tab) {
this.switchTabProfile(tab)
}
}
}
})
}
async switchTabProfile (tab: BaseTabComponent) {
const profile = await this.profilesService.showProfileSelector()
if (!profile) {
return
}
const params = await this.profilesService.newTabParametersForProfile(profile)
if (!params) {
return
}
if (!await tab.canClose()) {
return
}
const newTab = this.tabsService.create(params)
;(tab.parent as SplitTabComponent).replaceTab(tab, newTab)
tab.destroy()
}
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
if (!tabHeader && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
return [
{
label: 'Switch profile',
click: () => this.switchTabProfile(tab),
},
]
}
return []
}
}

View File

@@ -306,21 +306,6 @@ search-panel {
} }
} }
.btn.btn-outline-secondary {
@include button-outline-variant(#9badb9, #fff);
&:hover:not([disabled]), &:active:not([disabled]), &.active:not([disabled]) {
background-color: #3f484e;
border-color: darken(#9badb9, 25%);
}
border-color: darken(#9badb9, 25%);
&.disabled,
&:disabled {
color: #9badb9;
}
}
.btn-warning:not(:disabled):not(.disabled) { .btn-warning:not(:disabled):not(.disabled) {
&.active, &:active { &.active, &:active {
color: $gray-900; color: $gray-900;

View File

@@ -1 +0,0 @@
dist

View File

@@ -1,6 +1,6 @@
{ {
"name": "tabby-electron", "name": "tabby-electron",
"version": "1.0.145-nightly.0", "version": "1.0.148-nightly.2",
"description": "Electron-specific bindings", "description": "Electron-specific bindings",
"keywords": [ "keywords": [
"tabby-builtin-plugin" "tabby-builtin-plugin"
@@ -20,7 +20,6 @@
"@angular/core": "^9.1.9" "@angular/core": "^9.1.9"
}, },
"devDependencies": { "devDependencies": {
"axios": "^0.21.1",
"winston": "^3.3.3", "winston": "^3.3.3",
"electron-promise-ipc": "^2.2.4" "electron-promise-ipc": "^2.2.4"
} }

View File

@@ -2,7 +2,7 @@ import * as path from 'path'
import * as fs from 'fs/promises' import * as fs from 'fs/promises'
import * as fsSync from 'fs' import * as fsSync from 'fs'
import * as os from 'os' import * as os from 'os'
import promiseIpc from 'electron-promise-ipc' import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
import { execFile } from 'mz/child_process' import { execFile } from 'mz/child_process'
import { Injectable, NgZone } from '@angular/core' 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 } from 'tabby-core'
@@ -49,11 +49,11 @@ export class ElectronPlatformService extends PlatformService {
} }
async installPlugin (name: string, version: string): Promise<void> { async installPlugin (name: string, version: string): Promise<void> {
await (promiseIpc as any).send('plugin-manager:install', name, version) await (promiseIpc as RendererProcessType).send('plugin-manager:install', name, version)
} }
async uninstallPlugin (name: string): Promise<void> { async uninstallPlugin (name: string): Promise<void> {
await (promiseIpc as any).send('plugin-manager:uninstall', name) await (promiseIpc as RendererProcessType).send('plugin-manager:uninstall', name)
} }
async isProcessRunning (name: string): Promise<boolean> { async isProcessRunning (name: string): Promise<boolean> {
@@ -205,7 +205,7 @@ export class ElectronPlatformService extends PlatformService {
})) }))
} }
async startDownload (name: string, size: number): Promise<FileDownload|null> { async startDownload (name: string, mode: number, size: number): Promise<FileDownload|null> {
const result = await this.electron.dialog.showSaveDialog( const result = await this.electron.dialog.showSaveDialog(
this.hostWindow.getWindow(), this.hostWindow.getWindow(),
{ {
@@ -215,7 +215,7 @@ export class ElectronPlatformService extends PlatformService {
if (!result.filePath) { if (!result.filePath) {
return null return null
} }
const transfer = new ElectronFileDownload(result.filePath, size) const transfer = new ElectronFileDownload(result.filePath, mode, size)
await wrapPromise(this.zone, transfer.open()) await wrapPromise(this.zone, transfer.open())
this.fileTransferStarted.next(transfer) this.fileTransferStarted.next(transfer)
return transfer return transfer
@@ -230,6 +230,7 @@ export class ElectronPlatformService extends PlatformService {
class ElectronFileUpload extends FileUpload { class ElectronFileUpload extends FileUpload {
private size: number private size: number
private mode: number
private file: fs.FileHandle private file: fs.FileHandle
private buffer: Buffer private buffer: Buffer
@@ -239,7 +240,9 @@ class ElectronFileUpload extends FileUpload {
} }
async open (): Promise<void> { async open (): Promise<void> {
this.size = (await fs.stat(this.filePath)).size const stat = await fs.stat(this.filePath)
this.size = stat.size
this.mode = stat.mode
this.file = await fs.open(this.filePath, 'r') this.file = await fs.open(this.filePath, 'r')
} }
@@ -247,6 +250,10 @@ class ElectronFileUpload extends FileUpload {
return path.basename(this.filePath) return path.basename(this.filePath)
} }
getMode (): number {
return this.mode
}
getSize (): number { getSize (): number {
return this.size return this.size
} }
@@ -267,19 +274,24 @@ class ElectronFileDownload extends FileDownload {
constructor ( constructor (
private filePath: string, private filePath: string,
private mode: number,
private size: number, private size: number,
) { ) {
super() super()
} }
async open (): Promise<void> { async open (): Promise<void> {
this.file = await fs.open(this.filePath, 'w') this.file = await fs.open(this.filePath, 'w', this.mode)
} }
getName (): string { getName (): string {
return path.basename(this.filePath) return path.basename(this.filePath)
} }
getMode (): number {
return this.mode
}
getSize (): number { getSize (): number {
return this.size return this.size
} }

View File

@@ -16,13 +16,6 @@ async@^3.1.0:
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
call-bind@^1.0.0, call-bind@^1.0.2: call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@@ -150,11 +143,6 @@ fn.name@1.x.x:
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
function-bind@^1.1.1: function-bind@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"

View File

@@ -1 +0,0 @@
dist

View File

@@ -1,6 +1,6 @@
{ {
"name": "tabby-local", "name": "tabby-local",
"version": "1.0.145-nightly.0", "version": "1.0.148-nightly.2",
"description": "Tabby's local shell plugin", "description": "Tabby's local shell plugin",
"keywords": [ "keywords": [
"tabby-builtin-plugin" "tabby-builtin-plugin"
@@ -22,13 +22,11 @@
}, },
"devDependencies": { "devDependencies": {
"@types/deep-equal": "^1.0.0", "@types/deep-equal": "^1.0.0",
"@types/shell-escape": "^0.2.0",
"ansi-colors": "^4.1.1", "ansi-colors": "^4.1.1",
"dataurl": "0.1.0", "dataurl": "0.1.0",
"deep-equal": "2.0.5", "deep-equal": "2.0.5",
"ps-node": "^0.1.6", "ps-node": "^0.1.6",
"runes": "^0.4.2", "runes": "^0.4.2",
"shell-escape": "^0.2.0",
"utils-decorators": "^1.8.3" "utils-decorators": "^1.8.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -0,0 +1,41 @@
ng-container(*ngIf='!argvMode')
.form-group
label Command line
.input-group
.input-group-prepend
button.btn.btn-secondary((click)='switchToArgv()', title='Switch to split arguments')
i.fas.fa-fw.fa-caret-right
input.form-control.text-monospace(
[(ngModel)]='command',
(ngModelChange)='parseCommand()'
)
ng-container(*ngIf='argvMode')
.form-group
label Program
.input-group
.input-group-prepend
button.btn.btn-secondary((click)='switchToCommand()', title='Switch to a single-line command')
i.fas.fa-fw.fa-caret-down
input.form-control.text-monospace(
type='text',
[(ngModel)]='_model.command',
)
.form-group
label Arguments
.input-group(
*ngFor='let arg of _model.args; index as i; trackBy: trackByIndex',
)
input.form-control.text-monospace(
type='text',
[(ngModel)]='_model.args[i]',
)
.input-group-append
button.btn.btn-secondary((click)='_model.args.splice(i, 1)')
i.fas.fa-fw.fa-trash
.mt-2
button.btn.btn-secondary((click)='_model.args.push("")')
i.fas.fa-plus.mr-2
| Add

View File

@@ -0,0 +1,50 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import * as shellQuote from 'shell-quote'
import { Component, Input } from '@angular/core'
import { SessionOptions } from '../api'
/** @hidden */
@Component({
selector: 'command-line-editor',
template: require('./commandLineEditor.component.pug'),
})
export class CommandLineEditorComponent {
@Input() argvMode = false
@Input() _model: SessionOptions
command = ''
@Input() get model (): SessionOptions {
return this._model
}
set model (value: SessionOptions) {
this._model = value
this.updateCommand()
}
switchToCommand () {
this.updateCommand()
this.argvMode = false
}
switchToArgv () {
this.argvMode = true
}
parseCommand () {
const args = shellQuote.parse(this.command)
this.model.command = args[0] ?? ''
this.model.args = args.slice(1)
}
updateCommand () {
this.command = shellQuote.quote([
this.model.command,
...this.model.args ?? [],
])
}
trackByIndex (index) {
return index
}
}

View File

@@ -1,12 +1,12 @@
.mb-2.d-flex.align-items-center(*ngFor='let pair of vars') .mb-2.d-flex.align-items-center(*ngFor='let pair of vars')
.input-group .input-group
input.form-control.w-25([(ngModel)]='pair.key', (blur)='emitUpdate()', placeholder='Variable name') input.form-control.w-25.text-monospace([(ngModel)]='pair.key', (blur)='emitUpdate()', placeholder='Variable name')
.input-group-append .input-group-append
.input-group-text = .input-group-text =
input.form-control.w-50([(ngModel)]='pair.value', (blur)='emitUpdate()', placeholder='Value') input.form-control.w-50.text-monospace([(ngModel)]='pair.value', (blur)='emitUpdate()', placeholder='Value')
.input-group-append .input-group-append
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)') button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
i.fas.fa-trash i.fas.fa-fw.fa-trash
button.btn.btn-secondary((click)='addEnvironmentVar()') button.btn.btn-secondary((click)='addEnvironmentVar()')
i.fas.fa-plus.mr-2 i.fas.fa-plus.mr-2

View File

@@ -1,27 +1,4 @@
.form-group command-line-editor([model]='profile.options')
label Command
input.form-control(
type='text',
[(ngModel)]='profile.options.command',
)
.form-group
label Arguments
.input-group(
*ngFor='let arg of profile.options.args; index as i; trackBy: trackByIndex',
)
input.form-control(
type='text',
[(ngModel)]='profile.options.args[i]',
)
.input-group-append
button.btn.btn-secondary((click)='profile.options.args.splice(i, 1)')
i.fas.fa-trash
.mt-2
button.btn.btn-secondary((click)='profile.options.args.push("")')
i.fas.fa-plus.mr-2
| Add
.form-line(*ngIf='uac.isAvailable') .form-line(*ngIf='uac.isAvailable')
.header .header

View File

@@ -3,14 +3,14 @@ import { Component } from '@angular/core'
import { UACService } from '../services/uac.service' import { UACService } from '../services/uac.service'
import { LocalProfile } from '../api' import { LocalProfile } from '../api'
import { ElectronHostWindow, ElectronService } from 'tabby-electron' import { ElectronHostWindow, ElectronService } from 'tabby-electron'
import { ProfileSettingsComponent } from '../../../tabby-core/src/api/profileProvider' import { ProfileSettingsComponent } from 'tabby-core'
/** @hidden */ /** @hidden */
@Component({ @Component({
template: require('./localProfileSettings.component.pug'), template: require('./localProfileSettings.component.pug'),
}) })
export class LocalProfileSettingsComponent implements ProfileSettingsComponent { export class LocalProfileSettingsComponent implements ProfileSettingsComponent<LocalProfile> {
profile: LocalProfile profile: LocalProfile
constructor ( constructor (
@@ -40,8 +40,4 @@ export class LocalProfileSettingsComponent implements ProfileSettingsComponent {
)).filePaths )).filePaths
this.profile.options.cwd = paths[0] this.profile.options.cwd = paths[0]
} }
trackByIndex (index) {
return index
}
} }

View File

@@ -1,7 +1,7 @@
import { Component, Input, Injector } from '@angular/core' import { Component, Input, Injector } from '@angular/core'
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabby-core' import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabby-core'
import { BaseTerminalTabComponent } from 'tabby-terminal' import { BaseTerminalTabComponent } from 'tabby-terminal'
import { SessionOptions } from '../api' import { LocalProfile, SessionOptions } from '../api'
import { Session } from '../session' import { Session } from '../session'
import { UACService } from '../services/uac.service' import { UACService } from '../services/uac.service'
@@ -13,7 +13,8 @@ import { UACService } from '../services/uac.service'
animations: BaseTerminalTabComponent.animations, animations: BaseTerminalTabComponent.animations,
}) })
export class TerminalTabComponent extends BaseTerminalTabComponent { export class TerminalTabComponent extends BaseTerminalTabComponent {
@Input() sessionOptions: SessionOptions @Input() sessionOptions: SessionOptions // Deprecated
@Input() profile: LocalProfile
session: Session|null = null session: Session|null = null
// eslint-disable-next-line @typescript-eslint/no-useless-constructor // eslint-disable-next-line @typescript-eslint/no-useless-constructor
@@ -25,6 +26,8 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
} }
ngOnInit (): void { ngOnInit (): void {
this.sessionOptions = this.profile.options
this.logger = this.log.create('terminalTab') this.logger = this.log.create('terminalTab')
this.session = new Session(this.injector) this.session = new Session(this.injector)
@@ -49,17 +52,17 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
protected onFrontendReady (): void { protected onFrontendReady (): void {
this.initializeSession(this.size.columns, this.size.rows) this.initializeSession(this.size.columns, this.size.rows)
this.savedStateIsLive = this.sessionOptions.restoreFromPTYID === this.session?.getPTYID() this.savedStateIsLive = this.profile.options.restoreFromPTYID === this.session?.getPTYID()
super.onFrontendReady() super.onFrontendReady()
} }
initializeSession (columns: number, rows: number): void { initializeSession (columns: number, rows: number): void {
if (this.sessionOptions.runAsAdministrator && this.uac.isAvailable) { if (this.profile.options.runAsAdministrator && this.uac.isAvailable) {
this.sessionOptions = this.uac.patchSessionOptionsForUAC(this.sessionOptions) this.profile.options = this.uac.patchSessionOptionsForUAC(this.profile.options)
} }
this.session!.start({ this.session!.start({
...this.sessionOptions, ...this.profile.options,
width: columns, width: columns,
height: rows, height: rows,
}) })
@@ -71,11 +74,14 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
async getRecoveryToken (): Promise<any> { async getRecoveryToken (): Promise<any> {
const cwd = this.session ? await this.session.getWorkingDirectory() : null const cwd = this.session ? await this.session.getWorkingDirectory() : null
return { return {
type: 'app:terminal-tab', type: 'app:local-tab',
sessionOptions: { profile: {
...this.sessionOptions, ...this.profile,
cwd: cwd ?? this.sessionOptions.cwd, options: {
restoreFromPTYID: this.session?.getPTYID(), ...this.profile.options,
cwd: cwd ?? this.profile.options.cwd,
restoreFromPTYID: this.session?.getPTYID(),
},
}, },
savedState: this.frontend?.saveState(), savedState: this.frontend?.saveState(),
} }

View File

@@ -13,6 +13,7 @@ import { TerminalTabComponent } from './components/terminalTab.component'
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component' import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
import { EnvironmentEditorComponent } from './components/environmentEditor.component' import { EnvironmentEditorComponent } from './components/environmentEditor.component'
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component' import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
import { CommandLineEditorComponent } from './components/commandLineEditor.component'
import { TerminalService } from './services/terminal.service' import { TerminalService } from './services/terminal.service'
import { DockMenuService } from './services/dockMenu.service' import { DockMenuService } from './services/dockMenu.service'
@@ -91,16 +92,18 @@ import { LocalProfilesService } from './profiles'
TerminalTabComponent, TerminalTabComponent,
ShellSettingsTabComponent, ShellSettingsTabComponent,
LocalProfileSettingsComponent, LocalProfileSettingsComponent,
] as any[], ],
declarations: [ declarations: [
TerminalTabComponent, TerminalTabComponent,
ShellSettingsTabComponent, ShellSettingsTabComponent,
EnvironmentEditorComponent, EnvironmentEditorComponent,
CommandLineEditorComponent,
LocalProfileSettingsComponent, LocalProfileSettingsComponent,
] as any[], ],
exports: [ exports: [
TerminalTabComponent, TerminalTabComponent,
EnvironmentEditorComponent, EnvironmentEditorComponent,
CommandLineEditorComponent,
], ],
}) })
export default class LocalTerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class export default class LocalTerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -1,14 +1,30 @@
import deepClone from 'clone-deep'
import { Injectable, Inject } from '@angular/core' import { Injectable, Inject } from '@angular/core'
import { ProfileProvider, Profile, NewTabParameters, ConfigService, SplitTabComponent, AppService } from 'tabby-core' import { ProfileProvider, NewTabParameters, ConfigService, SplitTabComponent, AppService, PartialProfile } from 'tabby-core'
import { TerminalTabComponent } from './components/terminalTab.component' import { TerminalTabComponent } from './components/terminalTab.component'
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component' import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
import { ShellProvider, Shell, SessionOptions } from './api' import { ShellProvider, Shell, SessionOptions, LocalProfile } from './api'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class LocalProfilesService extends ProfileProvider { export class LocalProfilesService extends ProfileProvider<LocalProfile> {
id = 'local' id = 'local'
name = 'Local' name = 'Local'
settingsComponent = LocalProfileSettingsComponent settingsComponent = LocalProfileSettingsComponent
configDefaults = {
options: {
restoreFromPTYID: null,
command: '',
args: [],
cwd: null,
env: {
__nonStructural: true,
},
width: null,
height: null,
pauseAfterExit: false,
runAsAdministrator: false,
},
}
constructor ( constructor (
private app: AppService, private app: AppService,
@@ -18,7 +34,7 @@ export class LocalProfilesService extends ProfileProvider {
super() super()
} }
async getBuiltinProfiles (): Promise<Profile[]> { async getBuiltinProfiles (): Promise<PartialProfile<LocalProfile>[]> {
return (await this.getShells()).map(shell => ({ return (await this.getShells()).map(shell => ({
id: `local:${shell.id}`, id: `local:${shell.id}`,
type: 'local', type: 'local',
@@ -29,18 +45,20 @@ export class LocalProfilesService extends ProfileProvider {
})) }))
} }
async getNewTabParameters (profile: Profile): Promise<NewTabParameters<TerminalTabComponent>> { async getNewTabParameters (profile: PartialProfile<LocalProfile>): Promise<NewTabParameters<TerminalTabComponent>> {
const options = { ...profile.options } profile = deepClone(profile)
if (!options.cwd) { if (!profile.options?.cwd) {
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) { if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
options.cwd = await this.app.activeTab.session.getWorkingDirectory() profile.options ??= {}
profile.options.cwd = await this.app.activeTab.session.getWorkingDirectory() ?? undefined
} }
if (this.app.activeTab instanceof SplitTabComponent) { if (this.app.activeTab instanceof SplitTabComponent) {
const focusedTab = this.app.activeTab.getFocusedTab() const focusedTab = this.app.activeTab.getFocusedTab()
if (focusedTab instanceof TerminalTabComponent && focusedTab.session) { if (focusedTab instanceof TerminalTabComponent && focusedTab.session) {
options.cwd = await focusedTab.session.getWorkingDirectory() profile.options ??= {}
profile.options.cwd = await focusedTab.session.getWorkingDirectory() ?? undefined
} }
} }
} }
@@ -48,7 +66,7 @@ export class LocalProfilesService extends ProfileProvider {
return { return {
type: TerminalTabComponent, type: TerminalTabComponent,
inputs: { inputs: {
sessionOptions: options, profile,
}, },
} }
} }
@@ -66,7 +84,7 @@ export class LocalProfilesService extends ProfileProvider {
} }
} }
getDescription (profile: Profile): string { getDescription (profile: PartialProfile<LocalProfile>): string {
return profile.options?.command return profile.options?.command ?? ''
} }
} }

View File

@@ -7,14 +7,14 @@ import { TerminalTabComponent } from './components/terminalTab.component'
@Injectable() @Injectable()
export class RecoveryProvider extends TabRecoveryProvider<TerminalTabComponent> { export class RecoveryProvider extends TabRecoveryProvider<TerminalTabComponent> {
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> { async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
return recoveryToken.type === 'app:terminal-tab' return recoveryToken.type === 'app:local-tab'
} }
async recover (recoveryToken: RecoveryToken): Promise<NewTabParameters<TerminalTabComponent>> { async recover (recoveryToken: RecoveryToken): Promise<NewTabParameters<TerminalTabComponent>> {
return { return {
type: TerminalTabComponent, type: TerminalTabComponent,
inputs: { inputs: {
sessionOptions: recoveryToken.sessionOptions, profile: recoveryToken.profile,
savedState: recoveryToken.savedState, savedState: recoveryToken.savedState,
}, },
} }
@@ -23,9 +23,12 @@ export class RecoveryProvider extends TabRecoveryProvider<TerminalTabComponent>
duplicate (recoveryToken: RecoveryToken): RecoveryToken { duplicate (recoveryToken: RecoveryToken): RecoveryToken {
return { return {
...recoveryToken, ...recoveryToken,
sessionOptions: { profile: {
...recoveryToken.sessionOptions, ...recoveryToken.profile,
restoreFromPTYID: null, options: {
...recoveryToken.profile.options,
restoreFromPTYID: null,
},
}, },
savedState: null, savedState: null,
} }

View File

@@ -30,7 +30,7 @@ export class DockMenuService {
iconPath: process.execPath, iconPath: process.execPath,
iconIndex: 0, iconIndex: 0,
})), })),
}] : null as any) }] : null)
} }
if (this.hostApp.platform === Platform.macOS) { if (this.hostApp.platform === Platform.macOS) {
this.electron.app.dock.setMenu(this.electron.Menu.buildFromTemplate( this.electron.app.dock.setMenu(this.electron.Menu.buildFromTemplate(

View File

@@ -1,8 +1,8 @@
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Logger, LogService, ConfigService, AppService, ProfilesService } from 'tabby-core' import { Logger, LogService, ConfigService, ProfilesService, PartialProfile } from 'tabby-core'
import { TerminalTabComponent } from '../components/terminalTab.component' import { TerminalTabComponent } from '../components/terminalTab.component'
import { SessionOptions, LocalProfile } from '../api' import { LocalProfile } from '../api'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class TerminalService { export class TerminalService {
@@ -10,7 +10,6 @@ export class TerminalService {
/** @hidden */ /** @hidden */
private constructor ( private constructor (
private app: AppService,
private profilesService: ProfilesService, private profilesService: ProfilesService,
private config: ConfigService, private config: ConfigService,
log: LogService, log: LogService,
@@ -18,52 +17,43 @@ export class TerminalService {
this.logger = log.create('terminal') this.logger = log.create('terminal')
} }
async getDefaultProfile (): Promise<LocalProfile> { async getDefaultProfile (): Promise<PartialProfile<LocalProfile>> {
const profiles = await this.profilesService.getProfiles() const profiles = await this.profilesService.getProfiles()
let profile = profiles.find(x => x.id === this.config.store.terminal.profile) let profile = profiles.find(x => x.id === this.config.store.terminal.profile)
if (!profile) { if (!profile) {
profile = profiles.filter(x => x.type === 'local' && x.isBuiltin)[0] profile = profiles.filter(x => x.type === 'local' && x.isBuiltin)[0]
} }
return profile as LocalProfile return profile as PartialProfile<LocalProfile>
} }
/** /**
* Launches a new terminal with a specific shell and CWD * Launches a new terminal with a specific shell and CWD
* @param pause Wait for a keypress when the shell exits * @param pause Wait for a keypress when the shell exits
*/ */
async openTab (profile?: LocalProfile|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> { async openTab (profile?: PartialProfile<LocalProfile>|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> {
if (!profile) { if (!profile) {
profile = await this.getDefaultProfile() profile = await this.getDefaultProfile()
} }
cwd = cwd ?? profile.options.cwd const fullProfile = this.profilesService.getConfigProxyForProfile(profile)
cwd = cwd ?? fullProfile.options.cwd
if (cwd && !fs.existsSync(cwd)) { if (cwd && !fs.existsSync(cwd)) {
console.warn('Ignoring non-existent CWD:', cwd) console.warn('Ignoring non-existent CWD:', cwd)
cwd = null cwd = null
} }
this.logger.info(`Starting profile ${profile.name}`, profile) this.logger.info(`Starting profile ${fullProfile.name}`, fullProfile)
const options = { const options = {
...profile.options, ...fullProfile.options,
pauseAfterExit: pause, pauseAfterExit: pause,
cwd: cwd ?? undefined, cwd: cwd ?? undefined,
} }
return (await this.profilesService.openNewTabForProfile({ return (await this.profilesService.openNewTabForProfile({
...profile, ...fullProfile,
options, options,
})) as TerminalTabComponent })) as TerminalTabComponent
} }
/**
* Open a terminal with custom session options
*/
openTabWithOptions (sessionOptions: SessionOptions): TerminalTabComponent {
this.logger.info('Using session options:', sessionOptions)
return this.app.openNewTab({
type: TerminalTabComponent,
inputs: { sessionOptions },
})
}
} }

View File

@@ -104,8 +104,6 @@ export class Session extends BaseSession {
} }
start (options: SessionOptions): void { start (options: SessionOptions): void {
this.name = options.name ?? ''
let pty: PTYProxy|null = null let pty: PTYProxy|null = null
if (options.restoreFromPTYID) { if (options.restoreFromPTYID) {
@@ -162,7 +160,7 @@ export class Session extends BaseSession {
cwd, cwd,
env: env, env: env,
// `1` instead of `true` forces ConPTY even if unstable // `1` instead of `true` forces ConPTY even if unstable
useConpty: (isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY ? 1 : false) as any, useConpty: isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY ? 1 : false,
}) })
this.guessedCWD = cwd ?? null this.guessedCWD = cwd ?? null

View File

@@ -24,7 +24,7 @@ export class POSIXShellsProvider extends ShellProvider {
.filter(x => x && !x.startsWith('#')) .filter(x => x && !x.startsWith('#'))
.map(x => ({ .map(x => ({
id: slugify(x), id: slugify(x),
name: x.split('/')[2], name: x.split('/').pop(),
icon: 'fas fa-terminal', icon: 'fas fa-terminal',
command: x, command: x,
args: ['-l'], args: ['-l'],

View File

@@ -27,14 +27,14 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
click: async () => { click: async () => {
const modal = this.ngbModal.open(PromptModalComponent) const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = 'New profile name' modal.componentInstance.prompt = 'New profile name'
const name = (await modal.result)?.name const name = (await modal.result)?.value
if (!name) { if (!name) {
return return
} }
const profile = { const profile = {
options: { options: {
...tab.sessionOptions, ...tab.profile.options,
cwd: await tab.session?.getWorkingDirectory() ?? tab.sessionOptions.cwd, cwd: await tab.session?.getWorkingDirectory() ?? tab.profile.options.cwd,
}, },
name, name,
type: 'local', type: 'local',
@@ -74,7 +74,11 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
{ {
label: 'New terminal', label: 'New terminal',
click: () => { click: () => {
this.terminalService.openTabWithOptions((tab as any).sessionOptions) if (tab instanceof TerminalTabComponent) {
this.profilesService.openNewTabForProfile(tab.profile)
} else {
this.terminalService.openTab()
}
}, },
}, },
{ {
@@ -98,9 +102,12 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
submenu: profiles.map(profile => ({ submenu: profiles.map(profile => ({
label: profile.name, label: profile.name,
click: () => { click: () => {
this.terminalService.openTabWithOptions({ this.profilesService.openNewTabForProfile({
...profile.options, ...profile,
runAsAdministrator: true, options: {
...profile.options,
runAsAdministrator: true,
},
}) })
}, },
})), })),
@@ -111,9 +118,12 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
items.push({ items.push({
label: 'Duplicate as administrator', label: 'Duplicate as administrator',
click: () => { click: () => {
this.terminalService.openTabWithOptions({ this.profilesService.openNewTabForProfile({
...tab.sessionOptions, ...tab.profile,
runAsAdministrator: true, options: {
...tab.profile.options,
runAsAdministrator: true,
},
}) })
}, },
}) })

View File

@@ -7,11 +7,6 @@
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg== integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==
"@types/shell-escape@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@types/shell-escape/-/shell-escape-0.2.0.tgz#cd2f0df814388599dd07196dcc510de2669d1ed2"
integrity sha512-7kUdtJtUylvyISJbe9FMcvMTjRdP0EvNDO1WbT0lT22k/IPBiPRTpmWaKu5HTWLCGLQRWVHrzVHZktTDvvR23g==
ansi-colors@^4.1.1: ansi-colors@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@@ -357,11 +352,6 @@ runes@^0.4.2:
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355" resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg== integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==
shell-escape@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/shell-escape/-/shell-escape-0.2.0.tgz#68fd025eb0490b4f567a027f0bf22480b5f84133"
integrity sha1-aP0CXrBJC09WegJ/C/IkgLX4QTM=
side-channel@^1.0.3: side-channel@^1.0.3:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"

View File

@@ -1 +0,0 @@
dist

View File

@@ -1,6 +1,6 @@
{ {
"name": "tabby-plugin-manager", "name": "tabby-plugin-manager",
"version": "1.0.145-nightly.0", "version": "1.0.148-nightly.2",
"description": "Tabby's plugin manager", "description": "Tabby's plugin manager",
"keywords": [ "keywords": [
"tabby-builtin-plugin" "tabby-builtin-plugin"
@@ -18,7 +18,6 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/semver": "^7.1.0", "@types/semver": "^7.1.0",
"axios": "^0.21.1",
"semver": "^7.1.1" "semver": "^7.1.1"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -4,7 +4,7 @@
.d-flex .d-flex
h3.mb-1 Installed h3.mb-1 Installed
button.btn.btn-outline-secondary.btn-sm.ml-auto((click)='openPluginsFolder()') button.btn.btn-secondary.btn-sm.ml-auto((click)='openPluginsFolder()')
i.fas.fa-folder i.fas.fa-folder
span Plugins folder span Plugins folder

View File

@@ -7,18 +7,6 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.7.tgz#b9eb89d7dfa70d5d1ce525bc1411a35347f533a3" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.7.tgz#b9eb89d7dfa70d5d1ce525bc1411a35347f533a3"
integrity sha512-4g1jrL98mdOIwSOUh6LTlB0Cs9I0dQPwINUhBg7C6pN4HLr8GS8xsksJxilW6S6dQHVi2K/o+lQuQcg7LroCnw== integrity sha512-4g1jrL98mdOIwSOUh6LTlB0Cs9I0dQPwINUhBg7C6pN4HLr8GS8xsksJxilW6S6dQHVi2K/o+lQuQcg7LroCnw==
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
follow-redirects@^1.10.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7"
integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==
lru-cache@^6.0.0: lru-cache@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"

View File

View File

@@ -1,6 +1,6 @@
{ {
"name": "tabby-serial", "name": "tabby-serial",
"version": "1.0.145-nightly.0", "version": "1.0.148-nightly.2",
"description": "Serial connections for Tabby", "description": "Serial connections for Tabby",
"keywords": [ "keywords": [
"tabby-builtin-plugin" "tabby-builtin-plugin"
@@ -18,7 +18,8 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "14.14.14", "@types/node": "14.14.14",
"ansi-colors": "^4.1.1" "ansi-colors": "^4.1.1",
"serialport-binding-webserialapi": "^1.0.3"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/animations": "^9.1.9", "@angular/animations": "^9.1.9",

View File

@@ -4,6 +4,7 @@ import { LogService, NotificationsService, Profile } from 'tabby-core'
import { Subject, Observable } from 'rxjs' import { Subject, Observable } from 'rxjs'
import { Injector, NgZone } from '@angular/core' import { Injector, NgZone } from '@angular/core'
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal' import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
import { SerialService } from './services/serial.service'
export interface SerialProfile extends Profile { export interface SerialProfile extends Profile {
options: SerialProfileOptions options: SerialProfileOptions
@@ -19,7 +20,6 @@ export interface SerialProfileOptions extends StreamProcessingOptions, LoginScri
xon?: boolean xon?: boolean
xoff?: boolean xoff?: boolean
xany?: boolean xany?: boolean
color?: string
} }
export const BAUD_RATES = [ export const BAUD_RATES = [
@@ -39,9 +39,12 @@ export class SerialSession extends BaseSession {
private streamProcessor: TerminalStreamProcessor private streamProcessor: TerminalStreamProcessor
private zone: NgZone private zone: NgZone
private notifications: NotificationsService private notifications: NotificationsService
private serialService: SerialService
constructor (injector: Injector, public profile: SerialProfile) { constructor (injector: Injector, public profile: SerialProfile) {
super(injector.get(LogService).create(`serial-${profile.options.port}`)) super(injector.get(LogService).create(`serial-${profile.options.port}`))
this.serialService = injector.get(SerialService)
this.zone = injector.get(NgZone) this.zone = injector.get(NgZone)
this.notifications = injector.get(NotificationsService) this.notifications = injector.get(NotificationsService)
@@ -58,6 +61,10 @@ export class SerialSession extends BaseSession {
} }
async start (): Promise<void> { async start (): Promise<void> {
if (!this.profile.options.port) {
this.profile.options.port = (await this.serialService.listPorts())[0].name
}
this.serial = new SerialPort(this.profile.options.port, { this.serial = new SerialPort(this.profile.options.port, {
autoOpen: false, autoOpen: false,
baudRate: parseInt(this.profile.options.baudrate as any), baudRate: parseInt(this.profile.options.baudrate as any),

View File

@@ -3,7 +3,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
a(ngbNavLink) General a(ngbNavLink) General
ng-template(ngbNavContent) ng-template(ngbNavContent)
.row .row
.col-6 .col-6(ng:if='hostApp.platform !== Platform.Web')
.form-group .form-group
label Device label Device
input.form-control( input.form-control(

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { debounceTime, distinctUntilChanged, map } from 'rxjs' import { debounceTime, distinctUntilChanged, map } from 'rxjs'
import { ProfileSettingsComponent } from 'tabby-core' import { HostAppService, Platform, ProfileSettingsComponent } from 'tabby-core'
import { SerialPortInfo, BAUD_RATES, SerialProfile } from '../api' import { SerialPortInfo, BAUD_RATES, SerialProfile } from '../api'
import { SerialService } from '../services/serial.service' import { SerialService } from '../services/serial.service'
@@ -9,12 +9,14 @@ import { SerialService } from '../services/serial.service'
@Component({ @Component({
template: require('./serialProfileSettings.component.pug'), template: require('./serialProfileSettings.component.pug'),
}) })
export class SerialProfileSettingsComponent implements ProfileSettingsComponent { export class SerialProfileSettingsComponent implements ProfileSettingsComponent<SerialProfile> {
profile: SerialProfile profile: SerialProfile
foundPorts: SerialPortInfo[] foundPorts: SerialPortInfo[]
Platform = Platform
constructor ( constructor (
private serial: SerialService, private serial: SerialService,
public hostApp: HostAppService,
) { } ) { }
portsAutocomplete = text$ => text$.pipe(map(() => { portsAutocomplete = text$ => text$.pipe(map(() => {

View File

@@ -8,7 +8,7 @@
.mr-auto .mr-auto
button.btn.btn-sm.btn-link.mr-3((click)='changeBaudRate()', *ngIf='session && session.open') button.btn.btn-sm.btn-link.mr-3((click)='changeBaudRate()', *ngIf='session && session.open && hostApp.platform !== Platform.Web')
span Change baud rate span Change baud rate
button.btn.btn-sm.btn-link((click)='reconnect()', *ngIf='!session || !session.open') button.btn.btn-sm.btn-link((click)='reconnect()', *ngIf='!session || !session.open')

View File

@@ -2,7 +2,7 @@
import colors from 'ansi-colors' import colors from 'ansi-colors'
import { Component, Injector } from '@angular/core' import { Component, Injector } from '@angular/core'
import { first } from 'rxjs' import { first } from 'rxjs'
import { SelectorService } from 'tabby-core' import { Platform, SelectorService } from 'tabby-core'
import { BaseTerminalTabComponent } from 'tabby-terminal' import { BaseTerminalTabComponent } from 'tabby-terminal'
import { SerialSession, BAUD_RATES, SerialProfile } from '../api' import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
@@ -14,10 +14,10 @@ import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
animations: BaseTerminalTabComponent.animations, animations: BaseTerminalTabComponent.animations,
}) })
export class SerialTabComponent extends BaseTerminalTabComponent { export class SerialTabComponent extends BaseTerminalTabComponent {
enableToolbar = true
profile?: SerialProfile profile?: SerialProfile
session: SerialSession|null = null session: SerialSession|null = null
serialPort: any serialPort: any
Platform = Platform
// eslint-disable-next-line @typescript-eslint/no-useless-constructor // eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor ( constructor (
@@ -25,6 +25,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
private selector: SelectorService, private selector: SelectorService,
) { ) {
super(injector) super(injector)
this.enableToolbar = true
} }
ngOnInit () { ngOnInit () {

View File

@@ -1,48 +1,71 @@
import slugify from 'slugify' import slugify from 'slugify'
import SerialPort from 'serialport'
import WSABinding from 'serialport-binding-webserialapi'
import deepClone from 'clone-deep' import deepClone from 'clone-deep'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ProfileProvider, NewTabParameters, SelectorService } from 'tabby-core' import { ProfileProvider, NewTabParameters, SelectorService, HostAppService, Platform } from 'tabby-core'
import { InputMode, NewlineMode } from 'tabby-terminal'
import { SerialProfileSettingsComponent } from './components/serialProfileSettings.component' import { SerialProfileSettingsComponent } from './components/serialProfileSettings.component'
import { SerialTabComponent } from './components/serialTab.component' import { SerialTabComponent } from './components/serialTab.component'
import { SerialService } from './services/serial.service' import { SerialService } from './services/serial.service'
import { BAUD_RATES, SerialProfile } from './api' import { BAUD_RATES, SerialProfile } from './api'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class SerialProfilesService extends ProfileProvider { export class SerialProfilesService extends ProfileProvider<SerialProfile> {
id = 'serial' id = 'serial'
name = 'Serial' name = 'Serial'
settingsComponent = SerialProfileSettingsComponent settingsComponent = SerialProfileSettingsComponent
configDefaults = {
options: {
port: null,
baudrate: null,
databits: 8,
stopbits: 1,
parity: 'none',
rtscts: false,
xon: false,
xoff: false,
xany: false,
inputMode: 'local-echo',
outputMode: null,
inputNewlines: null,
outputNewlines: 'crlf',
scripts: [],
},
}
constructor ( constructor (
private selector: SelectorService, private selector: SelectorService,
private serial: SerialService, private serial: SerialService,
) { super() } private hostApp: HostAppService,
) {
super()
if (hostApp.platform === Platform.Web) {
SerialPort.Binding = WSABinding
}
}
async getBuiltinProfiles (): Promise<SerialProfile[]> { async getBuiltinProfiles (): Promise<SerialProfile[]> {
if (this.hostApp.platform === Platform.Web) {
return [
{
id: `serial:web`,
type: 'serial',
name: 'Serial connection',
icon: 'fas fa-microchip',
isBuiltin: true,
} as SerialProfile,
]
}
return [ return [
{ {
id: `serial:template`, id: `serial:template`,
type: 'serial', type: 'serial',
name: 'Serial connection', name: 'Serial connection',
icon: 'fas fa-microchip', icon: 'fas fa-microchip',
options: {
port: '',
databits: 8,
parity: 'none',
rtscts: false,
stopbits: 1,
xany: false,
xoff: false,
xon: false,
inputMode: 'local-echo' as InputMode,
outputMode: null,
inputNewlines: null,
outputNewlines: 'crlf' as NewlineMode,
},
isBuiltin: true, isBuiltin: true,
isTemplate: true, isTemplate: true,
}, } as SerialProfile,
...(await this.serial.listPorts()).map(p => ({ ...(await this.serial.listPorts()).map(p => ({
id: `serial:port-${slugify(p.name).replace('.', '-')}`, id: `serial:port-${slugify(p.name).replace('.', '-')}`,
type: 'serial', type: 'serial',
@@ -51,10 +74,8 @@ export class SerialProfilesService extends ProfileProvider {
isBuiltin: true, isBuiltin: true,
options: { options: {
port: p.name, port: p.name,
inputMode: 'local-echo' as InputMode,
outputNewlines: 'crlf' as NewlineMode,
}, },
})), } as SerialProfile)),
] ]
} }

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import SerialPort from 'serialport' import SerialPort from 'serialport'
import { ProfilesService } from 'tabby-core' import { PartialProfile, ProfilesService } from 'tabby-core'
import { SerialPortInfo, SerialProfile } from '../api' import { SerialPortInfo, SerialProfile } from '../api'
import { SerialTabComponent } from '../components/serialTab.component' import { SerialTabComponent } from '../components/serialTab.component'
@@ -24,19 +24,12 @@ export class SerialService {
baudrate = parseInt(path.split('@')[1]) baudrate = parseInt(path.split('@')[1])
path = path.split('@')[0] path = path.split('@')[0]
} }
const profile: SerialProfile = { const profile: PartialProfile<SerialProfile> = {
name: query, name: query,
type: 'serial', type: 'serial',
options: { options: {
port: path, port: path,
baudrate: baudrate, baudrate: baudrate,
databits: 8,
parity: 'none',
rtscts: false,
stopbits: 1,
xany: false,
xoff: false,
xon: false,
}, },
} }
window.localStorage.lastSerialConnection = JSON.stringify(profile) window.localStorage.lastSerialConnection = JSON.stringify(profile)

View File

@@ -2,6 +2,20 @@
# yarn lockfile v1 # yarn lockfile v1
"@serialport/binding-abstract@^9.0.2":
version "9.0.7"
resolved "https://registry.yarnpkg.com/@serialport/binding-abstract/-/binding-abstract-9.0.7.tgz#d2c7ecea0f100bdf20187bfc0d34ba90f5504e1e"
integrity sha512-g1ncCMIG9rMsxo/28ObYmXZcHThlvtZygsCANmyMUuFS7SwXY4+PhcEnt2+ZcMkEDNRiOklT+ngtIVx5GGpt/A==
dependencies:
debug "^4.3.1"
"@serialport/stream@^9.0.2":
version "9.0.7"
resolved "https://registry.yarnpkg.com/@serialport/stream/-/stream-9.0.7.tgz#0bf023eb0233a714fcc5a86de09e381e466d9882"
integrity sha512-c/h7HPAeFiryD9iTGlaSvPqHFHSZ0NMQHxC4rcmKS2Vu3qJuEtkBdTLABwsMp7iWEiSnI4KC3s7bHapaXP06FQ==
dependencies:
debug "^4.3.1"
"@types/node@14.14.14": "@types/node@14.14.14":
version "14.14.14" version "14.14.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
@@ -11,3 +25,23 @@ ansi-colors@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
debug@^4.3.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
serialport-binding-webserialapi@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/serialport-binding-webserialapi/-/serialport-binding-webserialapi-1.0.3.tgz#cf4348c075da2de8f6cf9936c0b95645f3ae657b"
integrity sha512-TS7dsvetVoTeiWlzpsT/akjtljiYPO56FoJWSFyJSoO/E8icYJ2neQ7CW5NW/sHZDnMqAxULyAny47UFhWz9oQ==
dependencies:
"@serialport/binding-abstract" "^9.0.2"
"@serialport/stream" "^9.0.2"

View File

@@ -1 +0,0 @@
dist

View File

@@ -1,6 +1,6 @@
{ {
"name": "tabby-settings", "name": "tabby-settings",
"version": "1.0.145-nightly.0", "version": "1.0.148-nightly.2",
"description": "Tabby terminal settings page", "description": "Tabby terminal settings page",
"keywords": [ "keywords": [
"tabby-builtin-plugin" "tabby-builtin-plugin"
@@ -18,6 +18,8 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/deep-equal": "1.0.1", "@types/deep-equal": "1.0.1",
"marked": "^2.1.3",
"ngx-infinite-scroll": "^10.0.1",
"utils-decorators": "^1.8.0" "utils-decorators": "^1.8.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -57,9 +57,9 @@
.mb-4 .mb-4
.col-12.col-lg-8 .col-12.col-lg-8(*ngIf='this.profileProvider.settingsComponent')
ng-template(#placeholder) ng-template(#placeholder)
.modal-footer .modal-footer
button.btn.btn-outline-primary((click)='save()') Save button.btn.btn-primary((click)='save()') Save
button.btn.btn-outline-danger((click)='cancel()') Cancel button.btn.btn-danger((click)='cancel()') Cancel

View File

@@ -2,7 +2,7 @@
import { Observable, OperatorFunction, debounceTime, map, distinctUntilChanged } from 'rxjs' import { Observable, OperatorFunction, debounceTime, map, distinctUntilChanged } from 'rxjs'
import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector } from '@angular/core' import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, Profile, ProfileProvider, ProfileSettingsComponent } from 'tabby-core' import { ConfigProxy, ConfigService, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService } from 'tabby-core'
const iconsData = require('../../../tabby-core/src/icons.json') const iconsData = require('../../../tabby-core/src/icons.json')
const iconsClassList = Object.keys(iconsData).map( const iconsClassList = Object.keys(iconsData).map(
@@ -15,18 +15,20 @@ const iconsClassList = Object.keys(iconsData).map(
@Component({ @Component({
template: require('./editProfileModal.component.pug'), template: require('./editProfileModal.component.pug'),
}) })
export class EditProfileModalComponent { export class EditProfileModalComponent<P extends Profile> {
@Input() profile: Profile @Input() profile: P & ConfigProxy
@Input() profileProvider: ProfileProvider @Input() profileProvider: ProfileProvider<P>
@Input() settingsComponent: new () => ProfileSettingsComponent @Input() settingsComponent: new () => ProfileSettingsComponent<P>
groupNames: string[] groupNames: string[]
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef @ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
private settingsComponentInstance: ProfileSettingsComponent private _profile: Profile
private settingsComponentInstance: ProfileSettingsComponent<P>
constructor ( constructor (
private injector: Injector, private injector: Injector,
private componentFactoryResolver: ComponentFactoryResolver, private componentFactoryResolver: ComponentFactoryResolver,
private profilesService: ProfilesService,
config: ConfigService, config: ConfigService,
private modalInstance: NgbActiveModal, private modalInstance: NgbActiveModal,
) { ) {
@@ -37,14 +39,22 @@ export class EditProfileModalComponent {
)].sort() as string[] )].sort() as string[]
} }
ngOnInit () {
this._profile = this.profile
this.profile = this.profilesService.getConfigProxyForProfile(this.profile)
}
ngAfterViewInit () { ngAfterViewInit () {
setTimeout(() => { const componentType = this.profileProvider.settingsComponent
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.profileProvider.settingsComponent) if (componentType) {
const componentRef = componentFactory.create(this.injector) setTimeout(() => {
this.settingsComponentInstance = componentRef.instance const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType)
this.settingsComponentInstance.profile = this.profile const componentRef = componentFactory.create(this.injector)
this.placeholder.insert(componentRef.hostView) this.settingsComponentInstance = componentRef.instance
}) this.settingsComponentInstance.profile = this.profile
this.placeholder.insert(componentRef.hostView)
})
}
} }
groupTypeahead = (text$: Observable<string>) => groupTypeahead = (text$: Observable<string>) =>
@@ -63,7 +73,8 @@ export class EditProfileModalComponent {
save () { save () {
this.profile.group ||= undefined this.profile.group ||= undefined
this.settingsComponentInstance.save?.() this.settingsComponentInstance.save?.()
this.modalInstance.close(this.profile) this.profile.__cleanup()
this.modalInstance.close(this._profile)
} }
cancel () { cancel () {

View File

@@ -9,4 +9,4 @@
div([style.width]='timeoutProgress + "%"') div([style.width]='timeoutProgress + "%"')
.modal-footer .modal-footer
button.btn.btn-outline-primary((click)='close()') Cancel button.btn.btn-primary((click)='close()') Cancel

View File

@@ -56,7 +56,7 @@ h3.mb-3 Profiles
*ngIf='group.editable && group.name', *ngIf='group.editable && group.name',
(click)='$event.stopPropagation(); deleteGroup(group)' (click)='$event.stopPropagation(); deleteGroup(group)'
) )
i.fas.fa-trash i.fas.fa-trash-alt
ng-container(*ngIf='!group.collapsed') ng-container(*ngIf='!group.collapsed')
ng-container(*ngFor='let profile of group.profiles') ng-container(*ngFor='let profile of group.profiles')
.list-group-item.pl-5.d-flex.align-items-center( .list-group-item.pl-5.d-flex.align-items-center(
@@ -85,10 +85,10 @@ h3.mb-3 Profiles
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); newProfile(profile)') button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); newProfile(profile)')
i.fas.fa-copy i.fas.fa-copy
button.btn.btn-link.text-danger.hover-reveal.ml-1( button.btn.btn-link.hover-reveal.ml-1(
*ngIf='!profile.isBuiltin', *ngIf='!profile.isBuiltin',
(click)='$event.stopPropagation(); deleteProfile(profile)' (click)='$event.stopPropagation(); deleteProfile(profile)'
) )
i.fas.fa-trash i.fas.fa-trash-alt
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}} .ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}

View File

@@ -3,12 +3,12 @@ import slugify from 'slugify'
import deepClone from 'clone-deep' import deepClone from 'clone-deep'
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent } from 'tabby-core' import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile } from 'tabby-core'
import { EditProfileModalComponent } from './editProfileModal.component' import { EditProfileModalComponent } from './editProfileModal.component'
interface ProfileGroup { interface ProfileGroup {
name?: string name?: string
profiles: Profile[] profiles: PartialProfile<Profile>[]
editable: boolean editable: boolean
collapsed: boolean collapsed: boolean
} }
@@ -19,9 +19,9 @@ interface ProfileGroup {
styles: [require('./profilesSettingsTab.component.scss')], styles: [require('./profilesSettingsTab.component.scss')],
}) })
export class ProfilesSettingsTabComponent extends BaseComponent { export class ProfilesSettingsTabComponent extends BaseComponent {
profiles: Profile[] = [] profiles: PartialProfile<Profile>[] = []
builtinProfiles: Profile[] = [] builtinProfiles: PartialProfile<Profile>[] = []
templateProfiles: Profile[] = [] templateProfiles: PartialProfile<Profile>[] = []
profileGroups: ProfileGroup[] profileGroups: ProfileGroup[]
filter = '' filter = ''
@@ -45,11 +45,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
this.subscribeUntilDestroyed(this.config.changed$, () => this.refresh()) this.subscribeUntilDestroyed(this.config.changed$, () => this.refresh())
} }
launchProfile (profile: Profile): void { launchProfile (profile: PartialProfile<Profile>): void {
this.profilesService.openNewTabForProfile(profile) this.profilesService.openNewTabForProfile(profile)
} }
async newProfile (base?: Profile): Promise<void> { async newProfile (base?: PartialProfile<Profile>): Promise<void> {
if (!base) { if (!base) {
const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles] const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles]
profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0)) profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
@@ -57,7 +57,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
'Select a base profile to use as a template', 'Select a base profile to use as a template',
profiles.map(p => ({ profiles.map(p => ({
icon: p.icon, icon: p.icon,
description: this.profilesService.providerForProfile(p)?.getDescription(p), description: this.profilesService.getDescription(p) ?? undefined,
name: p.group ? `${p.group} / ${p.name}` : p.name, name: p.group ? `${p.group} / ${p.name}` : p.name,
result: p, result: p,
})), })),
@@ -74,7 +74,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
await this.config.save() await this.config.save()
} }
async editProfile (profile: Profile): Promise<void> { async editProfile (profile: PartialProfile<Profile>): Promise<void> {
const modal = this.ngbModal.open( const modal = this.ngbModal.open(
EditProfileModalComponent, EditProfileModalComponent,
{ size: 'lg' }, { size: 'lg' },
@@ -82,11 +82,18 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
modal.componentInstance.profile = Object.assign({}, profile) modal.componentInstance.profile = Object.assign({}, profile)
modal.componentInstance.profileProvider = this.profilesService.providerForProfile(profile) modal.componentInstance.profileProvider = this.profilesService.providerForProfile(profile)
const result = await modal.result const result = await modal.result
// Fully replace the config
for (const k in profile) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete profile[k]
}
Object.assign(profile, result) Object.assign(profile, result)
await this.config.save() await this.config.save()
} }
async deleteProfile (profile: Profile): Promise<void> { async deleteProfile (profile: PartialProfile<Profile>): Promise<void> {
if ((await this.platform.showMessageBox( if ((await this.platform.showMessageBox(
{ {
type: 'warning', type: 'warning',
@@ -95,7 +102,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
defaultId: 0, defaultId: 0,
} }
)).response === 1) { )).response === 1) {
this.profilesService.providerForProfile(profile)?.deleteProfile(profile) this.profilesService.providerForProfile(profile)?.deleteProfile(
this.profilesService.getConfigProxyForProfile(profile))
this.config.store.profiles = this.config.store.profiles.filter(x => x !== profile) this.config.store.profiles = this.config.store.profiles.filter(x => x !== profile)
await this.config.save() await this.config.save()
} }
@@ -174,7 +182,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
return !this.filter || group.profiles.some(x => this.isProfileVisible(x)) return !this.filter || group.profiles.some(x => this.isProfileVisible(x))
} }
isProfileVisible (profile: Profile): boolean { isProfileVisible (profile: PartialProfile<Profile>): boolean {
return !this.filter || profile.name.toLowerCase().includes(this.filter.toLowerCase()) return !this.filter || profile.name.toLowerCase().includes(this.filter.toLowerCase())
} }
@@ -182,11 +190,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
return icon?.startsWith('<') ?? false return icon?.startsWith('<') ?? false
} }
getDescription (profile: Profile): string|null { getDescription (profile: PartialProfile<Profile>): string|null {
return this.profilesService.providerForProfile(profile)?.getDescription(profile) ?? null return this.profilesService.getDescription(profile)
} }
getTypeLabel (profile: Profile): string { getTypeLabel (profile: PartialProfile<Profile>): string {
const name = this.profilesService.providerForProfile(profile)?.name const name = this.profilesService.providerForProfile(profile)?.name
if (name === 'Local') { if (name === 'Local') {
return '' return ''
@@ -194,11 +202,12 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
return name ?? 'Unknown' return name ?? 'Unknown'
} }
getTypeColorClass (profile: Profile): string { getTypeColorClass (profile: PartialProfile<Profile>): string {
return { return {
ssh: 'secondary', ssh: 'secondary',
serial: 'success', serial: 'success',
telnet: 'info', telnet: 'info',
'split-layout': 'primary',
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning' }[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
} }
} }

View File

@@ -0,0 +1,12 @@
.container(
infiniteScroll,
[infiniteScrollDistance]='2',
[infiniteScrollThrottle]='50',
infiniteScrollContainer='release-notes-tab',
[fromRoot]='true',
(scrolled)='onScrolled()'
)
div(*ngFor='let release of releases')
h1 {{release.name}}
.text-muted {{release.version}} / {{release.date|date:'mediumDate'}}
section([fastHtmlBind]='release.content')

View File

@@ -0,0 +1,17 @@
:host {
overflow-y: scroll;
width: 100%;
padding: 30px;
}
::ng-deep img {
max-width: 100%;
}
h1 {
margin: 0;
}
section {
margin: 20px 0;
}

View File

@@ -0,0 +1,47 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import axios from 'axios'
import marked from 'marked'
import { Component } from '@angular/core'
import { BaseTabComponent } from 'tabby-core'
export interface Release {
name: string
version: string
content: string
date: Date
}
/** @hidden */
@Component({
selector: 'release-notes-tab',
template: require('./releaseNotesTab.component.pug'),
styles: [require('./releaseNotesTab.component.scss')],
})
export class ReleaseNotesComponent extends BaseTabComponent {
releases: Release[] = []
lastPage = 1
constructor () {
super()
this.setTitle('Release notes')
this.loadReleases(1)
}
async loadReleases (page) {
console.log('Loading releases page', page)
const response = await axios.get(`https://api.github.com/repos/eugeny/tabby/releases?page=${page}`, {
headers: { Accept: 'application/vnd.github.v3+json' },
})
this.releases = this.releases.concat(response.data.map(r => ({
name: r.name,
version: r.tag_name,
content: marked(r.body),
date: new Date(r.created_at),
})))
this.lastPage = page
}
onScrolled () {
this.loadReleases(this.lastPage + 1)
}
}

View File

@@ -16,5 +16,5 @@ h3.modal-header.m-0.pb-0 Set master passphrase
i.fas.fa-eye i.fas.fa-eye
.modal-footer .modal-footer
button.btn.btn-outline-primary((click)='ok()') Set passphrase button.btn.btn-primary((click)='ok()') Set passphrase
button.btn.btn-outline-danger((click)='cancel()') Cancel button.btn.btn-danger((click)='cancel()') Cancel

View File

@@ -1,4 +1,4 @@
button.btn.btn-outline-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()') Restart the app to apply changes
.content .content
ul.nav-pills(ngbNav, #nav='ngbNav', [activeId]='activeTab', orientation='vertical') ul.nav-pills(ngbNav, #nav='ngbNav', [activeId]='activeTab', orientation='vertical')
@@ -23,6 +23,12 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic
i.fas.fa-bug i.fas.fa-bug
span Report a problem span Report a problem
button.btn.btn-secondary.mr-3(
(click)='showReleaseNotes()',
)
i.fas.fa-book
span What's new
button.btn.btn-secondary( button.btn.btn-secondary(
*ngIf='!updateAvailable && hostApp.platform !== Platform.Web', *ngIf='!updateAvailable && hostApp.platform !== Platform.Web',
(click)='checkForUpdates()', (click)='checkForUpdates()',

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import { debounce } from 'utils-decorators/dist/cjs' import { debounce } from 'utils-decorators/dist/esm/debounce/debounce'
import { Component, Inject, Input, HostBinding, NgZone } from '@angular/core' import { Component, Inject, Input, HostBinding, NgZone } from '@angular/core'
import { import {
ConfigService, ConfigService,
@@ -11,9 +11,11 @@ import {
UpdaterService, UpdaterService,
PlatformService, PlatformService,
HostWindowService, HostWindowService,
AppService,
} from 'tabby-core' } from 'tabby-core'
import { SettingsTabProvider } from '../api' import { SettingsTabProvider } from '../api'
import { ReleaseNotesComponent } from './releaseNotesTab.component'
/** @hidden */ /** @hidden */
@Component({ @Component({
@@ -42,6 +44,7 @@ export class SettingsTabComponent extends BaseTabComponent {
public platform: PlatformService, public platform: PlatformService,
public zone: NgZone, public zone: NgZone,
private updater: UpdaterService, private updater: UpdaterService,
private app: AppService,
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[], @Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
) { ) {
super() super()
@@ -115,4 +118,10 @@ export class SettingsTabComponent extends BaseTabComponent {
this.updateAvailable = await this.updater.check() this.updateAvailable = await this.updater.check()
this.checkingForUpdate = false this.checkingForUpdate = false
} }
showReleaseNotes () {
this.app.openNewTabRaw({
type: ReleaseNotesComponent,
})
}
} }

View File

@@ -27,8 +27,35 @@ div(*ngIf='vault.isEnabled()')
.list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets') .list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets')
i.fas.fa-key i.fas.fa-key
.mr-auto {{getSecretLabel(secret)}} .mr-auto {{getSecretLabel(secret)}}
button.btn.btn-link((click)='removeSecret(secret)')
i.fas.fa-trash .hover-reveal(ngbDropdown)
button.btn.btn-link(ngbDropdownToggle)
i.fas.fa-ellipsis-v
div(ngbDropdownMenu)
button(
ngbDropdownItem,
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
(click)='renameFile(secret)'
)
i.fas.fa-fw.fa-pencil-alt
span Rename
button(
ngbDropdownItem,
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
(click)='replaceFileContent(secret)'
)
i.fas.fa-fw.fa-file-import
span Replace
button(
ngbDropdownItem,
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
(click)='exportFile(secret)'
)
i.fas.fa-fw.fa-file-export
span Export
button(ngbDropdownItem, (click)='removeSecret(secret)')
i.fas.fa-fw.fa-trash
span Delete
h3.mt-5 Options h3.mt-5 Options
.form-line .form-line

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE } from 'tabby-core' import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE, PromptModalComponent, VaultFileSecret } from 'tabby-core'
import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component' import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
@@ -12,6 +12,7 @@ import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.comp
}) })
export class VaultSettingsTabComponent extends BaseComponent { export class VaultSettingsTabComponent extends BaseComponent {
vaultContents: Vault|null = null vaultContents: Vault|null = null
VAULT_SECRET_TYPE_FILE = VAULT_SECRET_TYPE_FILE
constructor ( constructor (
public vault: VaultService, public vault: VaultService,
@@ -91,4 +92,51 @@ export class VaultSettingsTabComponent extends BaseComponent {
this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret) this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret)
this.vault.removeSecret(secret.type, secret.key) this.vault.removeSecret(secret.type, secret.key)
} }
async replaceFileContent (secret: VaultFileSecret) {
const transfers = await this.platform.startUpload()
if (!transfers.length) {
return
}
await this.vault.updateSecret(secret, {
...secret,
value: (await transfers[0].readAll()).toString('base64'),
})
this.loadVault()
}
async renameFile (secret: VaultFileSecret) {
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = 'New name'
modal.componentInstance.value = secret.key.description
const description = (await modal.result)?.value
if (!description) {
return
}
await this.vault.updateSecret(secret, {
...secret,
key: {
...secret.key,
description,
},
})
this.loadVault()
}
async exportFile (secret: VaultFileSecret) {
this.vault.forgetPassphrase()
secret = (await this.vault.getSecret(secret.type, secret.key)) as VaultFileSecret
const content = Buffer.from(secret.value, 'base64')
const download = await this.platform.startDownload(secret.key.description, 0o600, content.length)
if (download) {
await download.write(content)
download.close()
}
}
} }

View File

@@ -9,6 +9,17 @@ h3.mb-3 Window
) )
option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}} option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}}
.form-line(*ngIf='hostApp.platform === Platform.Web')
.header
.title Ask before closing the browser tab
.description Prevents accidental closing
toggle(
[(ngModel)]='config.store.web.preventAccidentalTabClosure',
(ngModelChange)='saveConfiguration()',
)
.form-line(*ngIf='platform.supportsWindowControls') .form-line(*ngIf='platform.supportsWindowControls')
.header .header
.title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background .title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { debounce } from 'utils-decorators/dist/cjs' import { debounce } from 'utils-decorators/dist/esm/debounce/debounce'
import { Component, Inject, NgZone, Optional } from '@angular/core' import { Component, Inject, NgZone, Optional } from '@angular/core'
import { import {
DockingService, DockingService,

View File

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser' import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { InfiniteScrollModule } from 'ngx-infinite-scroll'
import TabbyCorePlugin, { ToolbarButtonProvider, HotkeyProvider, ConfigProvider } from 'tabby-core' import TabbyCorePlugin, { ToolbarButtonProvider, HotkeyProvider, ConfigProvider } from 'tabby-core'
@@ -15,6 +16,7 @@ import { WindowSettingsTabComponent } from './components/windowSettingsTab.compo
import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component' import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
import { SetVaultPassphraseModalComponent } from './components/setVaultPassphraseModal.component' import { SetVaultPassphraseModalComponent } from './components/setVaultPassphraseModal.component'
import { ProfilesSettingsTabComponent } from './components/profilesSettingsTab.component' import { ProfilesSettingsTabComponent } from './components/profilesSettingsTab.component'
import { ReleaseNotesComponent } from './components/releaseNotesTab.component'
import { SettingsTabProvider } from './api' import { SettingsTabProvider } from './api'
import { ButtonProvider } from './buttonProvider' import { ButtonProvider } from './buttonProvider'
@@ -29,6 +31,7 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabP
FormsModule, FormsModule,
NgbModule, NgbModule,
TabbyCorePlugin, TabbyCorePlugin,
InfiniteScrollModule,
], ],
providers: [ providers: [
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }, { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
@@ -48,6 +51,7 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabP
SetVaultPassphraseModalComponent, SetVaultPassphraseModalComponent,
VaultSettingsTabComponent, VaultSettingsTabComponent,
WindowSettingsTabComponent, WindowSettingsTabComponent,
ReleaseNotesComponent,
], ],
declarations: [ declarations: [
EditProfileModalComponent, EditProfileModalComponent,
@@ -60,6 +64,7 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabP
SetVaultPassphraseModalComponent, SetVaultPassphraseModalComponent,
VaultSettingsTabComponent, VaultSettingsTabComponent,
WindowSettingsTabComponent, WindowSettingsTabComponent,
ReleaseNotesComponent,
], ],
}) })
export default class SettingsModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class export default class SettingsModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -2,11 +2,34 @@
# yarn lockfile v1 # yarn lockfile v1
"@scarf/scarf@^1.1.0":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.1.1.tgz#d8b9f20037b3a37dbf8dcdc4b3b72f9285bfce35"
integrity sha512-VGbKDbk1RFIaSmdVb0cNjjWJoRWRI/Weo23AjRCC2nryO0iAS8pzsToJfPVPtVs74WHw4L1UTADNdIYRLkirZQ==
"@types/deep-equal@1.0.1": "@types/deep-equal@1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03" resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg== integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==
marked@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753"
integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==
ngx-infinite-scroll@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz#6f51f2f8775a7c50d1dd8bad125d4e748abbe880"
integrity sha512-7is0eJZ9kJPsaHohRmMhJ/QFHAW9jp9twO5HcHRvFM/Yl/R8QCiokgjwmH0/CR3MuxUanxfHZMfO3PbYTwlBEg==
dependencies:
"@scarf/scarf" "^1.1.0"
opencollective-postinstall "^2.0.2"
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
tinyqueue@^2.0.3: tinyqueue@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"

View File

View File

@@ -1,6 +1,6 @@
{ {
"name": "tabby-ssh", "name": "tabby-ssh",
"version": "1.0.145-nightly.0", "version": "1.0.148-nightly.2",
"description": "SSH connections for Tabby", "description": "SSH connections for Tabby",
"keywords": [ "keywords": [
"tabby-builtin-plugin" "tabby-builtin-plugin"
@@ -12,7 +12,7 @@
"watch": "webpack --progress --color --watch", "watch": "webpack --progress --color --watch",
"postinstall": "run-script-os", "postinstall": "run-script-os",
"postinstall:darwin:linux": "exit", "postinstall:darwin:linux": "exit",
"postinstall:win32": "xcopy /i /y node_modules\\ssh2\\util\\pagent.exe util\\" "postinstall:win32": "xcopy /i /y ..\\node_modules\\ssh2\\util\\pagent.exe util\\"
}, },
"files": [ "files": [
"dist", "dist",
@@ -21,10 +21,9 @@
"author": "Eugene Pankov", "author": "Eugene Pankov",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "16.0.0", "@types/node": "16.0.1",
"@types/ssh2": "^0.5.46", "@types/ssh2": "^0.5.46",
"ansi-colors": "^4.1.1", "ansi-colors": "^4.1.1",
"ssh2": "^1.1.0",
"sshpk": "Eugeny/node-sshpk#89ed17dfae425a8b629873c8337e77d26838c04f", "sshpk": "Eugeny/node-sshpk#89ed17dfae425a8b629873c8337e77d26838c04f",
"strip-ansi": "^7.0.0" "strip-ansi": "^7.0.0"
}, },

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