prepared terminus-core for typedocs

This commit is contained in:
Eugene Pankov 2019-03-07 01:51:15 +01:00
parent cf5e31be79
commit 2ed35cb400
47 changed files with 766 additions and 219 deletions

2
.gitignore vendored
View File

@ -18,3 +18,5 @@ npm-debug.log
builtin-plugins builtin-plugins
package-lock.json package-lock.json
yarn-error.log yarn-error.log
docs/api

View File

@ -45,6 +45,7 @@
"tslint": "^5.12.0", "tslint": "^5.12.0",
"tslint-config-standard": "^8.0.1", "tslint-config-standard": "^8.0.1",
"tslint-eslint-rules": "^5.4.0", "tslint-eslint-rules": "^5.4.0",
"typedoc": "^0.14.2",
"typescript": "^3.1.3", "typescript": "^3.1.3",
"url-loader": "^1.1.1", "url-loader": "^1.1.1",
"val-loader": "0.5.0", "val-loader": "0.5.0",

29
terminus-core/README.md Normal file
View File

@ -0,0 +1,29 @@
Terminus Core Plugin
--------------------
* tabbed interface services
* toolbar UI
* config file management
* hotkeys
* tab recovery
* logging
* theming
Using the API:
```ts
import { AppService, TabContextMenuItemProvider } from 'terminus-core'
```
Exporting your subclasses:
```ts
@NgModule({
...
providers: [
...
{ provide: TabContextMenuItemProvider, useClass: MyContextMenu, multi: true },
...
]
})
```

View File

@ -1,4 +1,37 @@
/**
* Extend to add your own config options
*/
export abstract class ConfigProvider { export abstract class ConfigProvider {
/**
* Default values, e.g.
*
* ```ts
* defaults = {
* myPlugin: {
* foo: 1
* }
* }
* ```
*/
defaults: any = {} defaults: any = {}
platformDefaults: any = {}
/**
* [[Platform]] specific defaults, e.g.
*
* ```ts
* platformDefaults = {
* [Platform.Windows]: {
* myPlugin: {
* bar: true
* }
* },
* [Platform.macOS]: {
* myPlugin: {
* bar: false
* }
* },
* }
* ```
*/
platformDefaults: {[platform: string]: any} = {}
} }

View File

@ -1,8 +1,12 @@
export interface IHotkeyDescription { export interface IHotkeyDescription {
id: string, id: string
name: string, name: string
} }
/**
* Extend to provide your own hotkeys. A corresponding [[ConfigProvider]]
* must also provide the `hotkeys.foo` config options with the default values
*/
export abstract class HotkeyProvider { export abstract class HotkeyProvider {
hotkeys: IHotkeyDescription[] = [] hotkeys: IHotkeyDescription[] = []

View File

@ -1,6 +1,9 @@
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { TabHeaderComponent } from '../components/tabHeader.component' import { TabHeaderComponent } from '../components/tabHeader.component'
/**
* Extend to add items to the tab header's context menu
*/
export abstract class TabContextMenuItemProvider { export abstract class TabContextMenuItemProvider {
weight = 0 weight = 0

View File

@ -1,10 +1,38 @@
import { TabComponentType } from '../services/tabs.service' import { TabComponentType } from '../services/tabs.service'
export interface RecoveredTab { export interface RecoveredTab {
type: TabComponentType, /**
options?: any, * Component type to be instantiated
*/
type: TabComponentType
/**
* Component instance inputs
*/
options?: any
} }
/**
* Extend to enable recovery for your custom tab.
* This works in conjunction with [[getRecoveryToken()]]
*
* Terminus will try to find any [[TabRecoveryProvider]] that is able to process
* the recovery token previously returned by [[getRecoveryToken]].
*
* Recommended token format:
*
* ```json
* {
* type: 'my-tab-type',
* foo: 'bar',
* }
* ```
*/
export abstract class TabRecoveryProvider { export abstract class TabRecoveryProvider {
/**
* @param recoveryToken a recovery token found in the saved tabs list
* @returns [[RecoveredTab]] descriptor containing tab type and component inputs
* or `null` if this token is from a different tab type or is not supported
*/
abstract async recover (recoveryToken: any): Promise<RecoveredTab | null> abstract async recover (recoveryToken: any): Promise<RecoveredTab | null>
} }

View File

@ -1,5 +1,13 @@
/**
* Extend to add a custom CSS theme
*/
export abstract class Theme { export abstract class Theme {
name: string name: string
/**
* Complete CSS stylesheet
*/
css: string css: string
terminalBackground: string terminalBackground: string
} }

View File

@ -1,14 +1,34 @@
import { SafeHtml } from '@angular/platform-browser' import { SafeHtml } from '@angular/platform-browser'
/**
* See [[ToolbarButtonProvider]]
*/
export interface IToolbarButton { export interface IToolbarButton {
/**
* Raw SVG icon code
*/
icon: SafeHtml icon: SafeHtml
touchBarNSImage?: string
title: string title: string
/**
* Optional Touch Bar icon ID
*/
touchBarNSImage?: string
/**
* Optional Touch Bar button label
*/
touchBarTitle?: string touchBarTitle?: string
weight?: number weight?: number
click: () => void click: () => void
} }
/**
* Extend to add buttons to the toolbar
*/
export abstract class ToolbarButtonProvider { export abstract class ToolbarButtonProvider {
abstract provide (): IToolbarButton[] abstract provide (): IToolbarButton[]
} }

View File

@ -17,6 +17,7 @@ import { BaseTabComponent } from './baseTab.component'
import { SafeModeModalComponent } from './safeModeModal.component' import { SafeModeModalComponent } from './safeModeModal.component'
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api' import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
template: require('./appRoot.component.pug'), template: require('./appRoot.component.pug'),
@ -126,6 +127,11 @@ export class AppRootComponent {
this.onGlobalHotkey() this.onGlobalHotkey()
}) })
this.hostApp.windowCloseRequest$.subscribe(async () => {
await this.app.closeAllTabs()
this.hostApp.closeWindow()
})
if (window['safeModeReason']) { if (window['safeModeReason']) {
ngbModal.open(SafeModeModalComponent) ngbModal.open(SafeModeModalComponent)
} }

View File

@ -1,26 +1,58 @@
import { Observable, Subject } from 'rxjs' import { Observable, Subject } from 'rxjs'
import { ViewRef } from '@angular/core' import { ViewRef } from '@angular/core'
/**
* Represents an active "process" inside a tab,
* for example, a user process running inside a terminal tab
*/
export interface BaseTabProcess { export interface BaseTabProcess {
name: string name: string
} }
/**
* Abstract base class for custom tab components
*/
export abstract class BaseTabComponent { export abstract class BaseTabComponent {
/**
* Current tab title
*/
title: string title: string
/**
* User-defined title override
*/
customTitle: string customTitle: string
hasFocus = false
/**
* Last tab activity state
*/
hasActivity = false hasActivity = false
/**
* ViewRef to the tab DOM element
*/
hostView: ViewRef hostView: ViewRef
/**
* CSS color override for the tab's header
*/
color: string = null color: string = null
protected titleChange = new Subject<string>()
protected focused = new Subject<void>() protected hasFocus = false
protected blurred = new Subject<void>()
protected progress = new Subject<number>() /**
protected activity = new Subject<boolean>() * Ping this if your recovery state has been changed and you want
protected destroyed = new Subject<void>() * your tab state to be saved sooner
*/
protected recoveryStateChangedHint = new Subject<void>() protected recoveryStateChangedHint = new Subject<void>()
private progressClearTimeout: number private progressClearTimeout: number
private titleChange = new Subject<string>()
private focused = new Subject<void>()
private blurred = new Subject<void>()
private progress = new Subject<number>()
private activity = new Subject<boolean>()
private destroyed = new Subject<void>()
get focused$ (): Observable<void> { return this.focused } get focused$ (): Observable<void> { return this.focused }
get blurred$ (): Observable<void> { return this.blurred } get blurred$ (): Observable<void> { return this.blurred }
@ -46,6 +78,11 @@ export abstract class BaseTabComponent {
} }
} }
/**
* Sets visual progressbar on the tab
*
* @param {type} progress: value between 0 and 1, or `null` to remove
*/
setProgress (progress: number) { setProgress (progress: number) {
this.progress.next(progress) this.progress.next(progress)
if (progress) { if (progress) {
@ -58,24 +95,43 @@ export abstract class BaseTabComponent {
} }
} }
/**
* Shows the acticity marker on the tab header
*/
displayActivity (): void { displayActivity (): void {
this.hasActivity = true this.hasActivity = true
this.activity.next(true) this.activity.next(true)
} }
/**
* Removes the acticity marker from the tab header
*/
clearActivity (): void { clearActivity (): void {
this.hasActivity = false this.hasActivity = false
this.activity.next(false) this.activity.next(false)
} }
/**
* Override this and implement a [[TabRecoveryProvider]] to enable recovery
* for your custom tab
*
* @return JSON serializable tab state representation
* for your [[TabRecoveryProvider]] to parse
*/
async getRecoveryToken (): Promise<any> { async getRecoveryToken (): Promise<any> {
return null return null
} }
/**
* Override this to enable task completion notifications for the tab
*/
async getCurrentProcess (): Promise<BaseTabProcess> { async getCurrentProcess (): Promise<BaseTabProcess> {
return null return null
} }
/**
* Return false to prevent the tab from being closed
*/
async canClose (): Promise<boolean> { async canClose (): Promise<boolean> {
return true return true
} }
@ -88,6 +144,9 @@ export abstract class BaseTabComponent {
this.blurred.next() this.blurred.next()
} }
/**
* Called before the tab is closed
*/
destroy (): void { destroy (): void {
this.focused.complete() this.focused.complete()
this.blurred.complete() this.blurred.complete()

View File

@ -1,6 +1,7 @@
import { NgZone, Component, Input, HostBinding, HostListener } from '@angular/core' import { NgZone, Component, Input, HostBinding, HostListener } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
/** @hidden */
@Component({ @Component({
selector: 'checkbox', selector: 'checkbox',
template: require('./checkbox.component.pug'), template: require('./checkbox.component.pug'),

View File

@ -1,6 +1,7 @@
import { Component, Input, ElementRef, ViewChild } from '@angular/core' import { Component, Input, ElementRef, ViewChild } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
/** @hidden */
@Component({ @Component({
selector: 'rename-tab-modal', selector: 'rename-tab-modal',
template: require('./renameTabModal.component.pug'), template: require('./renameTabModal.component.pug'),

View File

@ -1,6 +1,7 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
/** @hidden */
@Component({ @Component({
template: require('./safeModeModal.component.pug'), template: require('./safeModeModal.component.pug'),
}) })

View File

@ -9,15 +9,30 @@ import { TabRecoveryService } from '../services/tabRecovery.service'
export declare type SplitOrientation = 'v' | 'h' export declare type SplitOrientation = 'v' | 'h'
export declare type SplitDirection = 'r' | 't' | 'b' | 'l' export declare type SplitDirection = 'r' | 't' | 'b' | 'l'
/**
* Describes a horizontal or vertical split row or column
*/
export class SplitContainer { export class SplitContainer {
orientation: SplitOrientation = 'h' orientation: SplitOrientation = 'h'
/**
* Children could be tabs or other containers
*/
children: (BaseTabComponent | SplitContainer)[] = [] children: (BaseTabComponent | SplitContainer)[] = []
/**
* Relative sizes of children, between 0 and 1. Total sum is 1
*/
ratios: number[] = [] ratios: number[] = []
x: number x: number
y: number y: number
w: number w: number
h: number h: number
/**
* @return Flat list of all tabs inside this container
*/
getAllTabs () { getAllTabs () {
let r = [] let r = []
for (let child of this.children) { for (let child of this.children) {
@ -30,6 +45,9 @@ export class SplitContainer {
return r return r
} }
/**
* Remove unnecessarily nested child containers and renormalizes [[ratios]]
*/
normalize () { normalize () {
for (let i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
let child = this.children[i] let child = this.children[i]
@ -64,6 +82,9 @@ export class SplitContainer {
this.ratios = this.ratios.map(x => x / s) this.ratios = this.ratios.map(x => x / s)
} }
/**
* Gets the left/top side offset for the given element index (between 0 and 1)
*/
getOffsetRatio (index: number): number { getOffsetRatio (index: number): number {
let s = 0 let s = 0
for (let i = 0; i < index; i++) { for (let i = 0; i < index; i++) {
@ -90,11 +111,22 @@ export class SplitContainer {
} }
} }
/**
* Represents a spanner (draggable border between two split areas)
*/
export interface SplitSpannerInfo { export interface SplitSpannerInfo {
container: SplitContainer container: SplitContainer
/**
* Number of the right/bottom split in the container
*/
index: number index: number
} }
/**
* Split tab is a tab that contains other tabs and allows further splitting them
* You'll mainly encounter it inside [[AppService]].tabs
*/
@Component({ @Component({
selector: 'split-tab', selector: 'split-tab',
template: ` template: `
@ -109,23 +141,43 @@ export interface SplitSpannerInfo {
styles: [require('./splitTab.component.scss')], styles: [require('./splitTab.component.scss')],
}) })
export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDestroy { export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
/** @hidden */
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef @ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
/**
* Top-level split container
*/
root: SplitContainer root: SplitContainer
/** @hidden */
_recoveredState: any _recoveredState: any
/** @hidden */
_spanners: SplitSpannerInfo[] = [] _spanners: SplitSpannerInfo[] = []
private focusedTab: BaseTabComponent private focusedTab: BaseTabComponent
private hotkeysSubscription: Subscription private hotkeysSubscription: Subscription
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map() private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
protected tabAdded = new Subject<BaseTabComponent>() private tabAdded = new Subject<BaseTabComponent>()
protected tabRemoved = new Subject<BaseTabComponent>() private tabRemoved = new Subject<BaseTabComponent>()
protected splitAdjusted = new Subject<SplitSpannerInfo>() private splitAdjusted = new Subject<SplitSpannerInfo>()
protected focusChanged = new Subject<BaseTabComponent>() private focusChanged = new Subject<BaseTabComponent>()
get tabAdded$ (): Observable<BaseTabComponent> { return this.tabAdded } get tabAdded$ (): Observable<BaseTabComponent> { return this.tabAdded }
get tabRemoved$ (): Observable<BaseTabComponent> { return this.tabRemoved } get tabRemoved$ (): Observable<BaseTabComponent> { return this.tabRemoved }
/**
* Fired when split ratio is changed for a given spanner
*/
get splitAdjusted$ (): Observable<SplitSpannerInfo> { return this.splitAdjusted } get splitAdjusted$ (): Observable<SplitSpannerInfo> { return this.splitAdjusted }
/**
* Fired when a different sub-tab gains focus
*/
get focusChanged$ (): Observable<BaseTabComponent> { return this.focusChanged } get focusChanged$ (): Observable<BaseTabComponent> { return this.focusChanged }
/** @hidden */
constructor ( constructor (
private hotkeys: HotkeysService, private hotkeys: HotkeysService,
private tabsService: TabsService, private tabsService: TabsService,
@ -174,6 +226,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
}) })
} }
/** @hidden */
async ngOnInit () { async ngOnInit () {
if (this._recoveredState) { if (this._recoveredState) {
await this.recoverContainer(this.root, this._recoveredState) await this.recoverContainer(this.root, this._recoveredState)
@ -185,10 +238,12 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
} }
} }
/** @hidden */
ngOnDestroy () { ngOnDestroy () {
this.hotkeysSubscription.unsubscribe() this.hotkeysSubscription.unsubscribe()
} }
/** @returns Flat list of all sub-tabs */
getAllTabs () { getAllTabs () {
return this.root.getAllTabs() return this.root.getAllTabs()
} }
@ -211,6 +266,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
this.layout() this.layout()
} }
/**
* Focuses the first available tab inside the given [[SplitContainer]]
*/
focusAnyIn (parent: BaseTabComponent | SplitContainer) { focusAnyIn (parent: BaseTabComponent | SplitContainer) {
if (!parent) { if (!parent) {
return return
@ -222,13 +280,16 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
} }
} }
addTab (tab: BaseTabComponent, relative: BaseTabComponent, dir: SplitDirection) { /**
* Inserts a new `tab` to the `side` of the `relative` tab
*/
addTab (tab: BaseTabComponent, relative: BaseTabComponent, side: SplitDirection) {
let target = this.getParentOf(relative) || this.root let target = this.getParentOf(relative) || this.root
let insertIndex = target.children.indexOf(relative) let insertIndex = target.children.indexOf(relative)
if ( if (
(target.orientation === 'v' && ['l', 'r'].includes(dir)) || (target.orientation === 'v' && ['l', 'r'].includes(side)) ||
(target.orientation === 'h' && ['t', 'b'].includes(dir)) (target.orientation === 'h' && ['t', 'b'].includes(side))
) { ) {
let newContainer = new SplitContainer() let newContainer = new SplitContainer()
newContainer.orientation = (target.orientation === 'v') ? 'h' : 'v' newContainer.orientation = (target.orientation === 'v') ? 'h' : 'v'
@ -242,7 +303,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
if (insertIndex === -1) { if (insertIndex === -1) {
insertIndex = 0 insertIndex = 0
} else { } else {
insertIndex += (dir === 'l' || dir === 't') ? 0 : 1 insertIndex += (side === 'l' || side === 't') ? 0 : 1
} }
for (let i = 0; i < target.children.length; i++) { for (let i = 0; i < target.children.length; i++) {
@ -278,6 +339,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
} }
} }
/**
* Moves focus in the given direction
*/
navigate (dir: SplitDirection) { navigate (dir: SplitDirection) {
let rel: BaseTabComponent | SplitContainer = this.focusedTab let rel: BaseTabComponent | SplitContainer = this.focusedTab
let parent = this.getParentOf(rel) let parent = this.getParentOf(rel)
@ -309,6 +373,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
this.addTab(newTab, tab, dir) this.addTab(newTab, tab, dir)
} }
/**
* @returns the immediate parent of `tab`
*/
getParentOf (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer { getParentOf (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer {
root = root || this.root root = root || this.root
for (let child of root.children) { for (let child of root.children) {
@ -325,18 +392,22 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
return null return null
} }
/** @hidden */
async canClose (): Promise<boolean> { async canClose (): Promise<boolean> {
return !(await Promise.all(this.getAllTabs().map(x => x.canClose()))).some(x => !x) return !(await Promise.all(this.getAllTabs().map(x => x.canClose()))).some(x => !x)
} }
/** @hidden */
async getRecoveryToken (): Promise<any> { async getRecoveryToken (): Promise<any> {
return this.root.serialize() return this.root.serialize()
} }
/** @hidden */
async getCurrentProcess (): Promise<BaseTabProcess> { async getCurrentProcess (): Promise<BaseTabProcess> {
return (await Promise.all(this.getAllTabs().map(x => x.getCurrentProcess()))).find(x => !!x) return (await Promise.all(this.getAllTabs().map(x => x.getCurrentProcess()))).find(x => !!x)
} }
/** @hidden */
onSpannerAdjusted (spanner: SplitSpannerInfo) { onSpannerAdjusted (spanner: SplitSpannerInfo) {
this.layout() this.layout()
this.splitAdjusted.next(spanner) this.splitAdjusted.next(spanner)
@ -433,6 +504,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
} }
} }
/** @hidden */
@Injectable() @Injectable()
export class SplitTabRecoveryProvider extends TabRecoveryProvider { export class SplitTabRecoveryProvider extends TabRecoveryProvider {
async recover (recoveryToken: any): Promise<RecoveredTab> { async recover (recoveryToken: any): Promise<RecoveredTab> {

View File

@ -1,6 +1,7 @@
import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core' import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core'
import { SplitContainer } from './splitTab.component' import { SplitContainer } from './splitTab.component'
/** @hidden */
@Component({ @Component({
selector: 'split-tab-spanner', selector: 'split-tab-spanner',
template: '', template: '',

View File

@ -3,6 +3,7 @@ import { ConfigService } from '../services/config.service'
import { HomeBaseService } from '../services/homeBase.service' import { HomeBaseService } from '../services/homeBase.service'
import { IToolbarButton, ToolbarButtonProvider } from '../api' import { IToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */
@Component({ @Component({
selector: 'start-page', selector: 'start-page',
template: require('./startPage.component.pug'), template: require('./startPage.component.pug'),

View File

@ -1,6 +1,7 @@
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core' import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
/** @hidden */
@Component({ @Component({
selector: 'tab-body', selector: 'tab-body',
template: ` template: `

View File

@ -9,6 +9,7 @@ import { ElectronService } from '../services/electron.service'
import { AppService } from '../services/app.service' import { AppService } from '../services/app.service'
import { HostAppService, Platform } from '../services/hostApp.service' import { HostAppService, Platform } from '../services/hostApp.service'
/** @hidden */
@Component({ @Component({
selector: 'tab-header', selector: 'tab-header',
template: require('./tabHeader.component.pug'), template: require('./tabHeader.component.pug'),

View File

@ -1,5 +1,6 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
/** @hidden */
@Component({ @Component({
selector: 'title-bar', selector: 'title-bar',
template: require('./titleBar.component.pug'), template: require('./titleBar.component.pug'),

View File

@ -2,6 +2,7 @@ import { Component } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms' import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { CheckboxComponent } from './checkbox.component' import { CheckboxComponent } from './checkbox.component'
/** @hidden */
@Component({ @Component({
selector: 'toggle', selector: 'toggle',
template: ` template: `

View File

@ -9,7 +9,7 @@ button.btn.btn-secondary.btn-maximize(
svg(version='1.1', width='10', height='10') svg(version='1.1', width='10', height='10')
path(d='M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z') path(d='M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z')
button.btn.btn-secondary.btn-close( button.btn.btn-secondary.btn-close(
(click)='app.closeWindow()' (click)='closeWindow()'
) )
svg(version='1.1', width='10', height='10') svg(version='1.1', width='10', height='10')
path(d='M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z') path(d='M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z')

View File

@ -2,6 +2,7 @@ import { Component } from '@angular/core'
import { HostAppService } from '../services/hostApp.service' import { HostAppService } from '../services/hostApp.service'
import { AppService } from '../services/app.service' import { AppService } from '../services/app.service'
/** @hidden */
@Component({ @Component({
selector: 'window-controls', selector: 'window-controls',
template: require('./windowControls.component.pug'), template: require('./windowControls.component.pug'),
@ -9,4 +10,9 @@ import { AppService } from '../services/app.service'
}) })
export class WindowControlsComponent { export class WindowControlsComponent {
constructor (public hostApp: HostAppService, public app: AppService) { } constructor (public hostApp: HostAppService, public app: AppService) { }
async closeWindow () {
await this.app.closeAllTabs()
this.hostApp.closeWindow()
}
} }

View File

@ -1,6 +1,7 @@
import { ConfigProvider } from './api/configProvider' import { ConfigProvider } from './api/configProvider'
import { Platform } from './services/hostApp.service' import { Platform } from './services/hostApp.service'
/** @hidden */
export class CoreConfigProvider extends ConfigProvider { export class CoreConfigProvider extends ConfigProvider {
platformDefaults = { platformDefaults = {
[Platform.macOS]: require('./configDefaults.macos.yaml'), [Platform.macOS]: require('./configDefaults.macos.yaml'),

View File

@ -1,5 +1,6 @@
import { Directive, AfterViewInit, ElementRef } from '@angular/core' import { Directive, AfterViewInit, ElementRef } from '@angular/core'
/** @hidden */
@Directive({ @Directive({
selector: '[autofocus]' selector: '[autofocus]'
}) })

View File

@ -0,0 +1,117 @@
import { Injectable } from '@angular/core'
import { IHotkeyDescription, HotkeyProvider } from './api/hotkeyProvider'
/** @hidden */
@Injectable()
export class AppHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [
{
id: 'new-window',
name: 'New window',
},
{
id: 'toggle-window',
name: 'Toggle terminal window',
},
{
id: 'toggle-fullscreen',
name: 'Toggle fullscreen mode',
},
{
id: 'rename-tab',
name: 'Rename Tab',
},
{
id: 'close-tab',
name: 'Close tab',
},
{
id: 'toggle-last-tab',
name: 'Toggle last tab',
},
{
id: 'next-tab',
name: 'Next tab',
},
{
id: 'previous-tab',
name: 'Previous tab',
},
{
id: 'tab-1',
name: 'Tab 1',
},
{
id: 'tab-2',
name: 'Tab 2',
},
{
id: 'tab-3',
name: 'Tab 3',
},
{
id: 'tab-4',
name: 'Tab 4',
},
{
id: 'tab-5',
name: 'Tab 5',
},
{
id: 'tab-6',
name: 'Tab 6',
},
{
id: 'tab-7',
name: 'Tab 7',
},
{
id: 'tab-8',
name: 'Tab 8',
},
{
id: 'tab-9',
name: 'Tab 9',
},
{
id: 'tab-10',
name: 'Tab 10',
},
{
id: 'split-right',
name: 'Split to the right',
},
{
id: 'split-bottom',
name: 'Split to the bottom',
},
{
id: 'split-left',
name: 'Split to the left',
},
{
id: 'split-top',
name: 'Split to the top',
},
{
id: 'split-nav-up',
name: 'Focus the pane above',
},
{
id: 'split-nav-down',
name: 'Focus the pane below',
},
{
id: 'split-nav-left',
name: 'Focus the pane on the left',
},
{
id: 'split-nav-right',
name: 'Focus the pane on the right',
},
]
async provide (): Promise<IHotkeyDescription[]> {
return this.hotkeys
}
}

View File

@ -6,8 +6,6 @@ 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 { DndModule } from 'ng2-dnd' import { DndModule } from 'ng2-dnd'
import { AppHotkeyProvider } from './services/hotkeys.service'
import { AppRootComponent } from './components/appRoot.component' import { AppRootComponent } from './components/appRoot.component'
import { CheckboxComponent } from './components/checkbox.component' import { CheckboxComponent } from './components/checkbox.component'
import { TabBodyComponent } from './components/tabBody.component' import { TabBodyComponent } from './components/tabBody.component'
@ -31,6 +29,7 @@ import { TabRecoveryProvider } from './api/tabRecovery'
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 { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu } from './tabContextMenu' import { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu } from './tabContextMenu'
import 'perfect-scrollbar/css/perfect-scrollbar.css' import 'perfect-scrollbar/css/perfect-scrollbar.css'
@ -49,6 +48,7 @@ const PROVIDERS = [
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } } { provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } }
] ]
/** @hidden */
@NgModule({ @NgModule({
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -3,7 +3,6 @@ import { takeUntil } from 'rxjs/operators'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { SplitTabComponent } from '../components/splitTab.component' import { SplitTabComponent } from '../components/splitTab.component'
import { Logger, LogService } from './log.service'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import { HostAppService } from './hostApp.service' import { HostAppService } from './hostApp.service'
import { TabRecoveryService } from './tabRecovery.service' import { TabRecoveryService } from './tabRecovery.service'
@ -39,9 +38,11 @@ class CompletionObserver {
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class AppService { export class AppService {
tabs: BaseTabComponent[] = [] tabs: BaseTabComponent[] = []
activeTab: BaseTabComponent
lastTabIndex = 0 get activeTab (): BaseTabComponent { return this._activeTab }
logger: Logger
private lastTabIndex = 0
private _activeTab: BaseTabComponent
private activeTabChange = new Subject<BaseTabComponent>() private activeTabChange = new Subject<BaseTabComponent>()
private tabsChanged = new Subject<void>() private tabsChanged = new Subject<void>()
@ -55,19 +56,17 @@ export class AppService {
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened } get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
get tabsChanged$ (): Observable<void> { return this.tabsChanged } get tabsChanged$ (): Observable<void> { return this.tabsChanged }
get tabClosed$ (): Observable<BaseTabComponent> { return this.tabClosed } get tabClosed$ (): Observable<BaseTabComponent> { return this.tabClosed }
/** Fires once when the app is ready */
get ready$ (): Observable<void> { return this.ready } get ready$ (): Observable<void> { return this.ready }
/** @hidden */
constructor ( constructor (
private config: ConfigService, private config: ConfigService,
private hostApp: HostAppService, private hostApp: HostAppService,
private tabRecovery: TabRecoveryService, private tabRecovery: TabRecoveryService,
private tabsService: TabsService, private tabsService: TabsService,
log: LogService,
) { ) {
this.logger = log.create('app')
this.hostApp.windowCloseRequest$.subscribe(() => this.closeWindow())
this.tabRecovery.recoverTabs().then(tabs => { this.tabRecovery.recoverTabs().then(tabs => {
for (let tab of tabs) { for (let tab of tabs) {
this.openNewTabRaw(tab.type, tab.options) this.openNewTabRaw(tab.type, tab.options)
@ -82,7 +81,7 @@ export class AppService {
}) })
} }
addTabRaw (tab: BaseTabComponent) { private addTabRaw (tab: BaseTabComponent) {
this.tabs.push(tab) this.tabs.push(tab)
this.selectTab(tab) this.selectTab(tab)
this.tabsChanged.next() this.tabsChanged.next()
@ -93,7 +92,7 @@ export class AppService {
}) })
tab.titleChange$.subscribe(title => { tab.titleChange$.subscribe(title => {
if (tab === this.activeTab) { if (tab === this._activeTab) {
this.hostApp.setTitle(title) this.hostApp.setTitle(title)
} }
}) })
@ -101,7 +100,7 @@ export class AppService {
tab.destroyed$.subscribe(() => { tab.destroyed$.subscribe(() => {
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1) let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
this.tabs = this.tabs.filter((x) => x !== tab) this.tabs = this.tabs.filter((x) => x !== tab)
if (tab === this.activeTab) { if (tab === this._activeTab) {
this.selectTab(this.tabs[newIndex]) this.selectTab(this.tabs[newIndex])
} }
this.tabsChanged.next() this.tabsChanged.next()
@ -109,12 +108,20 @@ export class AppService {
}) })
} }
/**
* Adds a new tab **without** wrapping it in a SplitTabComponent
* @param inputs Properties to be assigned on the new tab component instance
*/
openNewTabRaw (type: TabComponentType, inputs?: any): BaseTabComponent { openNewTabRaw (type: TabComponentType, inputs?: any): BaseTabComponent {
let tab = this.tabsService.create(type, inputs) let tab = this.tabsService.create(type, inputs)
this.addTabRaw(tab) this.addTabRaw(tab)
return tab return tab
} }
/**
* Adds a new tab while wrapping it in a SplitTabComponent
* @param inputs Properties to be assigned on the new tab component instance
*/
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent { openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
let splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent let splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
let tab = this.tabsService.create(type, inputs) let tab = this.tabsService.create(type, inputs)
@ -124,29 +131,30 @@ export class AppService {
} }
selectTab (tab: BaseTabComponent) { selectTab (tab: BaseTabComponent) {
if (this.activeTab === tab) { if (this._activeTab === tab) {
this.activeTab.emitFocused() this._activeTab.emitFocused()
return return
} }
if (this.tabs.includes(this.activeTab)) { if (this.tabs.includes(this._activeTab)) {
this.lastTabIndex = this.tabs.indexOf(this.activeTab) this.lastTabIndex = this.tabs.indexOf(this._activeTab)
} else { } else {
this.lastTabIndex = null this.lastTabIndex = null
} }
if (this.activeTab) { if (this._activeTab) {
this.activeTab.clearActivity() this._activeTab.clearActivity()
this.activeTab.emitBlurred() this._activeTab.emitBlurred()
} }
this.activeTab = tab this._activeTab = tab
this.activeTabChange.next(tab) this.activeTabChange.next(tab)
if (this.activeTab) { if (this._activeTab) {
setImmediate(() => { setImmediate(() => {
this.activeTab.emitFocused() this._activeTab.emitFocused()
}) })
this.hostApp.setTitle(this.activeTab.title) this.hostApp.setTitle(this._activeTab.title)
} }
} }
/** Switches between the current tab and the previously active one */
toggleLastTab () { toggleLastTab () {
if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) { if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) {
this.lastTabIndex = 0 this.lastTabIndex = 0
@ -156,7 +164,7 @@ export class AppService {
nextTab () { nextTab () {
if (this.tabs.length > 1) { if (this.tabs.length > 1) {
let tabIndex = this.tabs.indexOf(this.activeTab) let tabIndex = this.tabs.indexOf(this._activeTab)
if (tabIndex < this.tabs.length - 1) { if (tabIndex < this.tabs.length - 1) {
this.selectTab(this.tabs[tabIndex + 1]) this.selectTab(this.tabs[tabIndex + 1])
} else if (this.config.store.appearance.cycleTabs) { } else if (this.config.store.appearance.cycleTabs) {
@ -167,7 +175,7 @@ export class AppService {
previousTab () { previousTab () {
if (this.tabs.length > 1) { if (this.tabs.length > 1) {
let tabIndex = this.tabs.indexOf(this.activeTab) let tabIndex = this.tabs.indexOf(this._activeTab)
if (tabIndex > 0) { if (tabIndex > 0) {
this.selectTab(this.tabs[tabIndex - 1]) this.selectTab(this.tabs[tabIndex - 1])
} else if (this.config.store.appearance.cycleTabs) { } else if (this.config.store.appearance.cycleTabs) {
@ -176,6 +184,7 @@ export class AppService {
} }
} }
/** @hidden */
emitTabsChanged () { emitTabsChanged () {
this.tabsChanged.next() this.tabsChanged.next()
} }
@ -197,7 +206,7 @@ export class AppService {
} }
} }
async closeWindow () { async closeAllTabs () {
for (let tab of this.tabs) { for (let tab of this.tabs) {
if (!await tab.canClose()) { if (!await tab.canClose()) {
return return
@ -206,15 +215,19 @@ export class AppService {
for (let tab of this.tabs) { for (let tab of this.tabs) {
tab.destroy() tab.destroy()
} }
this.hostApp.closeWindow()
} }
/** @hidden */
emitReady () { emitReady () {
this.ready.next(null) this.ready.next(null)
this.ready.complete() this.ready.complete()
this.hostApp.emitReady() this.hostApp.emitReady()
} }
/**
* Returns an observable that fires once
* the tab's internal "process" (see [[BaseTabProcess]]) completes
*/
observeTabCompletion (tab: BaseTabComponent): Observable<void> { observeTabCompletion (tab: BaseTabComponent): Observable<void> {
if (!this.completionObservers.has(tab)) { if (!this.completionObservers.has(tab)) {
let observer = new CompletionObserver(tab) let observer = new CompletionObserver(tab)

View File

@ -18,6 +18,7 @@ function isNonStructuralObjectMember (v) {
return v instanceof Object && !(v instanceof Array) && v.__nonStructural return v instanceof Object && !(v instanceof Array) && v.__nonStructural
} }
/** @hidden */
export class ConfigProxy { export class ConfigProxy {
constructor (real: any, defaults: any) { constructor (real: any, defaults: any) {
for (let key in defaults) { for (let key in defaults) {
@ -76,9 +77,21 @@ export class ConfigProxy {
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ConfigService { export class ConfigService {
/**
* Contains the actual config values
*/
store: any store: any
/**
* Whether an app restart is required due to recent changes
*/
restartRequested: boolean restartRequested: boolean
/**
* Full config file path
*/
path: string path: string
private changed = new Subject<void>() private changed = new Subject<void>()
private _store: any private _store: any
private defaults: any private defaults: any
@ -86,6 +99,7 @@ export class ConfigService {
get changed$ (): Observable<void> { return this.changed } get changed$ (): Observable<void> { return this.changed }
/** @hidden */
constructor ( constructor (
electron: ElectronService, electron: ElectronService,
private hostApp: HostAppService, private hostApp: HostAppService,
@ -129,10 +143,16 @@ export class ConfigService {
this.hostApp.broadcastConfigChange() this.hostApp.broadcastConfigChange()
} }
/**
* Reads config YAML as string
*/
readRaw (): string { readRaw (): string {
return yaml.safeDump(this._store) return yaml.safeDump(this._store)
} }
/**
* Writes config YAML as string
*/
writeRaw (data: string): void { writeRaw (data: string): void {
this._store = yaml.safeLoad(data) this._store = yaml.safeLoad(data)
this.save() this.save()
@ -140,7 +160,7 @@ export class ConfigService {
this.emitChange() this.emitChange()
} }
emitChange (): void { private emitChange (): void {
this.changed.next() this.changed.next()
} }
@ -148,6 +168,12 @@ export class ConfigService {
this.restartRequested = true this.restartRequested = true
} }
/**
* Filters a list of Angular services to only include those provided
* by plugins that are enabled
*
* @typeparam T Base provider type
*/
enabledServices<T> (services: T[]): T[] { enabledServices<T> (services: T[]): T[] {
if (!this.servicesCache) { if (!this.servicesCache) {
this.servicesCache = {} this.servicesCache = {}

View File

@ -10,6 +10,7 @@ export interface IScreen {
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class DockingService { export class DockingService {
/** @hidden */
constructor ( constructor (
private electron: ElectronService, private electron: ElectronService,
private config: ConfigService, private config: ConfigService,
@ -78,7 +79,7 @@ export class DockingService {
}) })
} }
repositionWindow () { private repositionWindow () {
let [x, y] = this.hostApp.getWindow().getPosition() let [x, y] = this.hostApp.getWindow().getPosition()
for (let screen of this.electron.screen.getAllDisplays()) { for (let screen of this.electron.screen.getAllDisplays()) {
let bounds = screen.bounds let bounds = screen.bounds

View File

@ -24,6 +24,7 @@ export class ElectronService {
MenuItem: typeof MenuItem MenuItem: typeof MenuItem
private electron: any private electron: any
/** @hidden */
constructor () { constructor () {
this.electron = require('electron') this.electron = require('electron')
this.remote = this.electron.remote this.remote = this.electron.remote
@ -42,18 +43,9 @@ export class ElectronService {
this.MenuItem = this.remote.MenuItem this.MenuItem = this.remote.MenuItem
} }
remoteRequire (name: string): any { /**
return this.remote.require(name) * Removes OS focus from Terminus' window
} */
remoteRequirePluginModule (plugin: string, module: string, globals: any): any {
return this.remoteRequire(this.remoteResolvePluginModule(plugin, module, globals))
}
remoteResolvePluginModule (plugin: string, module: string, globals: any): any {
return globals.require.resolve(`${plugin}/node_modules/${module}`)
}
loseFocus () { loseFocus () {
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
this.remote.Menu.sendActionToFirstResponder('hide:') this.remote.Menu.sendActionToFirstResponder('hide:')

View File

@ -9,6 +9,7 @@ import uuidv4 = require('uuid/v4')
export class HomeBaseService { export class HomeBaseService {
appVersion: string appVersion: string
/** @hidden */
constructor ( constructor (
private electron: ElectronService, private electron: ElectronService,
private config: ConfigService, private config: ConfigService,

View File

@ -16,12 +16,19 @@ export interface Bounds {
height: number height: number
} }
/**
* Provides interaction with the main process
*/
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class HostAppService { export class HostAppService {
platform: Platform platform: Platform
nodePlatform: string
/**
* Fired once the window is visible
*/
shown = new EventEmitter<any>() shown = new EventEmitter<any>()
isFullScreen = false isFullScreen = false
private preferencesMenu = new Subject<void>() private preferencesMenu = new Subject<void>()
private secondInstance = new Subject<void>() private secondInstance = new Subject<void>()
private cliOpenDirectory = new Subject<string>() private cliOpenDirectory = new Subject<string>()
@ -35,29 +42,62 @@ export class HostAppService {
private logger: Logger private logger: Logger
private windowId: number private windowId: number
/**
* Fired when Preferences is selected in the macOS menu
*/
get preferencesMenu$ (): Observable<void> { return this.preferencesMenu } get preferencesMenu$ (): Observable<void> { return this.preferencesMenu }
/**
* Fired when a second instance of Terminus is launched
*/
get secondInstance$ (): Observable<void> { return this.secondInstance } get secondInstance$ (): Observable<void> { return this.secondInstance }
/**
* Fired for the `terminus open` CLI command
*/
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory } get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
/**
* Fired for the `terminus run` CLI command
*/
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand } get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
/**
* Fired for the `terminus paste` CLI command
*/
get cliPaste$ (): Observable<string> { return this.cliPaste } get cliPaste$ (): Observable<string> { return this.cliPaste }
/**
* Fired for the `terminus profile` CLI command
*/
get cliOpenProfile$ (): Observable<string> { return this.cliOpenProfile } get cliOpenProfile$ (): Observable<string> { return this.cliOpenProfile }
/**
* Fired when another window modified the config file
*/
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast } get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
/**
* Fired when the window close button is pressed
*/
get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest } get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest }
get windowMoved$ (): Observable<void> { return this.windowMoved } get windowMoved$ (): Observable<void> { return this.windowMoved }
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged } get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
/** @hidden */
constructor ( constructor (
private zone: NgZone, private zone: NgZone,
private electron: ElectronService, private electron: ElectronService,
log: LogService, log: LogService,
) { ) {
this.logger = log.create('hostApp') this.logger = log.create('hostApp')
this.nodePlatform = require('os').platform()
this.platform = { this.platform = {
win32: Platform.Windows, win32: Platform.Windows,
darwin: Platform.macOS, darwin: Platform.macOS,
linux: Platform.Linux linux: Platform.Linux
}[this.nodePlatform] }[process.platform]
this.windowId = parseInt(location.search.substring(1)) this.windowId = parseInt(location.search.substring(1))
this.logger.info('Window ID:', this.windowId) this.logger.info('Window ID:', this.windowId)
@ -117,6 +157,9 @@ export class HostAppService {
})) }))
} }
/**
* Returns the current remote [[BrowserWindow]]
*/
getWindow () { getWindow () {
return this.electron.BrowserWindow.fromId(this.windowId) return this.electron.BrowserWindow.fromId(this.windowId)
} }
@ -125,18 +168,6 @@ export class HostAppService {
this.electron.ipcRenderer.send('app:new-window') this.electron.ipcRenderer.send('app:new-window')
} }
getShell () {
return this.electron.shell
}
getAppPath () {
return this.electron.app.getAppPath()
}
getPath (type: string) {
return this.electron.app.getPath(type)
}
toggleFullscreen () { toggleFullscreen () {
let window = this.getWindow() let window = this.getWindow()
window.setFullScreen(!this.isFullScreen) window.setFullScreen(!this.isFullScreen)
@ -174,6 +205,11 @@ export class HostAppService {
this.electron.ipcRenderer.send('window-set-always-on-top', flag) this.electron.ipcRenderer.send('window-set-always-on-top', flag)
} }
/**
* Sets window vibrancy mode (Windows, macOS)
*
* @param type `null`, or `fluent` when supported (Windowd only)
*/
setVibrancy (enable: boolean, type: string) { setVibrancy (enable: boolean, type: string) {
document.body.classList.toggle('vibrant', enable) document.body.classList.toggle('vibrant', enable)
if (this.platform === Platform.macOS) { if (this.platform === Platform.macOS) {
@ -196,6 +232,9 @@ export class HostAppService {
this.electron.Menu.buildFromTemplate(menuDefinition).popup({}) this.electron.Menu.buildFromTemplate(menuDefinition).popup({})
} }
/**
* Notifies other windows of config file changes
*/
broadcastConfigChange () { broadcastConfigChange () {
this.electron.ipcRenderer.send('app:config-change') this.electron.ipcRenderer.send('app:config-change')
} }

View File

@ -5,16 +5,16 @@ import { ConfigService } from '../services/config.service'
import { ElectronService } from '../services/electron.service' import { ElectronService } from '../services/electron.service'
export interface PartialHotkeyMatch { export interface PartialHotkeyMatch {
id: string, id: string
strokes: string[], strokes: string[]
matchedLength: number, matchedLength: number
} }
const KEY_TIMEOUT = 2000 const KEY_TIMEOUT = 2000
interface EventBufferEntry { interface EventBufferEntry {
event: NativeKeyEvent, event: NativeKeyEvent
time: number, time: number
} }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@ -26,6 +26,7 @@ export class HotkeysService {
private disabledLevel = 0 private disabledLevel = 0
private hotkeyDescriptions: IHotkeyDescription[] = [] private hotkeyDescriptions: IHotkeyDescription[] = []
/** @hidden */
constructor ( constructor (
private zone: NgZone, private zone: NgZone,
private electron: ElectronService, private electron: ElectronService,
@ -51,11 +52,20 @@ export class HotkeysService {
}) })
} }
/**
* Adds a new key event to the buffer
*
* @param name DOM event name
* @param nativeEvent event object
*/
pushKeystroke (name, nativeEvent) { pushKeystroke (name, nativeEvent) {
nativeEvent.event = name nativeEvent.event = name
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() }) this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
} }
/**
* Check the buffer for new complete keystrokes
*/
processKeystrokes () { processKeystrokes () {
if (this.isEnabled()) { if (this.isEnabled()) {
this.zone.run(() => { this.zone.run(() => {
@ -84,7 +94,7 @@ export class HotkeysService {
return stringifyKeySequence(this.currentKeystrokes.map(x => x.event)) return stringifyKeySequence(this.currentKeystrokes.map(x => x.event))
} }
registerGlobalHotkey () { private registerGlobalHotkey () {
this.electron.globalShortcut.unregisterAll() this.electron.globalShortcut.unregisterAll()
let value = this.config.store.hotkeys['toggle-window'] || [] let value = this.config.store.hotkeys['toggle-window'] || []
if (typeof value === 'string') { if (typeof value === 'string') {
@ -103,11 +113,11 @@ export class HotkeysService {
}) })
} }
getHotkeysConfig () { private getHotkeysConfig () {
return this.getHotkeysConfigRecursive(this.config.store.hotkeys) return this.getHotkeysConfigRecursive(this.config.store.hotkeys)
} }
getHotkeysConfigRecursive (branch) { private getHotkeysConfigRecursive (branch) {
let keys = {} let keys = {}
for (let key in branch) { for (let key in branch) {
let value = branch[key] let value = branch[key]
@ -129,7 +139,7 @@ export class HotkeysService {
return keys return keys
} }
getCurrentFullyMatchedHotkey (): string { private getCurrentFullyMatchedHotkey (): string {
let currentStrokes = this.getCurrentKeystrokes() let currentStrokes = this.getCurrentKeystrokes()
let config = this.getHotkeysConfig() let config = this.getHotkeysConfig()
for (let id in config) { for (let id in config) {
@ -199,117 +209,3 @@ export class HotkeysService {
).reduce((a, b) => a.concat(b)) ).reduce((a, b) => a.concat(b))
} }
} }
@Injectable()
export class AppHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [
{
id: 'new-window',
name: 'New window',
},
{
id: 'toggle-window',
name: 'Toggle terminal window',
},
{
id: 'toggle-fullscreen',
name: 'Toggle fullscreen mode',
},
{
id: 'rename-tab',
name: 'Rename Tab',
},
{
id: 'close-tab',
name: 'Close tab',
},
{
id: 'toggle-last-tab',
name: 'Toggle last tab',
},
{
id: 'next-tab',
name: 'Next tab',
},
{
id: 'previous-tab',
name: 'Previous tab',
},
{
id: 'tab-1',
name: 'Tab 1',
},
{
id: 'tab-2',
name: 'Tab 2',
},
{
id: 'tab-3',
name: 'Tab 3',
},
{
id: 'tab-4',
name: 'Tab 4',
},
{
id: 'tab-5',
name: 'Tab 5',
},
{
id: 'tab-6',
name: 'Tab 6',
},
{
id: 'tab-7',
name: 'Tab 7',
},
{
id: 'tab-8',
name: 'Tab 8',
},
{
id: 'tab-9',
name: 'Tab 9',
},
{
id: 'tab-10',
name: 'Tab 10',
},
{
id: 'split-right',
name: 'Split to the right',
},
{
id: 'split-bottom',
name: 'Split to the bottom',
},
{
id: 'split-left',
name: 'Split to the left',
},
{
id: 'split-top',
name: 'Split to the top',
},
{
id: 'split-nav-up',
name: 'Focus the pane above',
},
{
id: 'split-nav-down',
name: 'Focus the pane below',
},
{
id: 'split-nav-left',
name: 'Focus the pane on the left',
},
{
id: 'split-nav-right',
name: 'Focus the pane on the right',
},
]
async provide (): Promise<IHotkeyDescription[]> {
return this.hotkeys
}
}

View File

@ -11,13 +11,13 @@ export const altKeyName = {
}[process.platform] }[process.platform]
export interface NativeKeyEvent { export interface NativeKeyEvent {
event?: string, event?: string
altKey: boolean, altKey: boolean
ctrlKey: boolean, ctrlKey: boolean
metaKey: boolean, metaKey: boolean
shiftKey: boolean, shiftKey: boolean
key: string, key: string
keyCode: string, keyCode: string
} }
export function stringifyKeySequence (events: NativeKeyEvent[]): string[] { export function stringifyKeySequence (events: NativeKeyEvent[]): string[] {

View File

@ -39,7 +39,7 @@ export class Logger {
private name: string, private name: string,
) {} ) {}
doLog (level: string, ...args: any[]) { private doLog (level: string, ...args: any[]) {
console[level](`%c[${this.name}]`, 'color: #aaa', ...args) console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
if (this.winstonLogger) { if (this.winstonLogger) {
this.winstonLogger[level](...args) this.winstonLogger[level](...args)
@ -57,6 +57,7 @@ export class Logger {
export class LogService { export class LogService {
private log: any private log: any
/** @hidden */
constructor (electron: ElectronService) { constructor (electron: ElectronService) {
this.log = initializeWinston(electron) this.log = initializeWinston(electron)
} }

View File

@ -37,7 +37,7 @@ export class ShellIntegrationService {
this.updatePaths() this.updatePaths()
} }
async updatePaths (): Promise<void> { private async updatePaths (): Promise<void> {
// Update paths in case of an update // Update paths in case of an update
if (this.hostApp.platform === Platform.Windows) { if (this.hostApp.platform === Platform.Windows) {
if (await this.isInstalled()) { if (await this.isInstalled()) {

View File

@ -4,6 +4,7 @@ import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from '../services/log.service' import { Logger, LogService } from '../services/log.service'
import { ConfigService } from '../services/config.service' import { ConfigService } from '../services/config.service'
/** @hidden */
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class TabRecoveryService { export class TabRecoveryService {
logger: Logger logger: Logger

View File

@ -6,12 +6,16 @@ export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class TabsService { export class TabsService {
/** @hidden */
constructor ( constructor (
private componentFactoryResolver: ComponentFactoryResolver, private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector, private injector: Injector,
private tabRecovery: TabRecoveryService, private tabRecovery: TabRecoveryService,
) { } ) { }
/**
* Instantiates a tab component and assigns given inputs
*/
create (type: TabComponentType, inputs?: any): BaseTabComponent { create (type: TabComponentType, inputs?: any): BaseTabComponent {
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type) let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
let componentRef = componentFactory.create(this.injector) let componentRef = componentFactory.create(this.injector)
@ -21,6 +25,9 @@ export class TabsService {
return tab return tab
} }
/**
* Duplicates an existing tab instance (using the tab recovery system)
*/
async duplicate (tab: BaseTabComponent): Promise<BaseTabComponent> { async duplicate (tab: BaseTabComponent): Promise<BaseTabComponent> {
let token = await tab.getRecoveryToken() let token = await tab.getRecoveryToken()
if (!token) { if (!token) {
@ -32,5 +39,4 @@ export class TabsService {
} }
return null return null
} }
} }

View File

@ -6,6 +6,7 @@ import { Theme } from '../api/theme'
export class ThemesService { export class ThemesService {
private styleElement: HTMLElement = null private styleElement: HTMLElement = null
/** @hidden */
constructor ( constructor (
private config: ConfigService, private config: ConfigService,
@Inject(Theme) private themes: Theme[], @Inject(Theme) private themes: Theme[],
@ -34,7 +35,7 @@ export class ThemesService {
document.querySelector('style#custom-css').innerHTML = this.config.store.appearance.css document.querySelector('style#custom-css').innerHTML = this.config.store.appearance.css
} }
applyCurrentTheme (): void { private applyCurrentTheme (): void {
this.applyTheme(this.findCurrentTheme()) this.applyTheme(this.findCurrentTheme())
} }
} }

View File

@ -6,6 +6,7 @@ import { ElectronService } from './electron.service'
import { HostAppService, Platform } from './hostApp.service' import { HostAppService, Platform } from './hostApp.service'
import { IToolbarButton, ToolbarButtonProvider } from '../api' import { IToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class TouchbarService { export class TouchbarService {
private tabsSegmentedControl: TouchBarSegmentedControl private tabsSegmentedControl: TouchBarSegmentedControl

View File

@ -6,6 +6,7 @@ import { ElectronService } from './electron.service'
const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest' const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest'
/** @hidden */
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class UpdaterService { export class UpdaterService {
private logger: Logger private logger: Logger

View File

@ -4,6 +4,7 @@ import { BaseTabComponent } from './components/baseTab.component'
import { TabHeaderComponent } from './components/tabHeader.component' import { TabHeaderComponent } from './components/tabHeader.component'
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider' import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
/** @hidden */
@Injectable() @Injectable()
export class CloseContextMenu extends TabContextMenuItemProvider { export class CloseContextMenu extends TabContextMenuItemProvider {
weight = -5 weight = -5
@ -61,6 +62,7 @@ const COLORS = [
{ name: 'Yellow', value: '#ffd500' }, { name: 'Yellow', value: '#ffd500' },
] ]
/** @hidden */
@Injectable() @Injectable()
export class CommonOptionsContextMenu extends TabContextMenuItemProvider { export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
weight = -1 weight = -1
@ -98,6 +100,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
} }
} }
/** @hidden */
@Injectable() @Injectable()
export class TaskCompletionContextMenu extends TabContextMenuItemProvider { export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
constructor ( constructor (
@ -121,7 +124,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
type: 'checkbox', type: 'checkbox',
checked: (tab as any).__completionNotificationEnabled, checked: (tab as any).__completionNotificationEnabled,
click: () => this.zone.run(() => { click: () => this.zone.run(() => {
;(tab as any).__completionNotificationEnabled = !(tab as any).__completionNotificationEnabled (tab as any).__completionNotificationEnabled = !(tab as any).__completionNotificationEnabled
if ((tab as any).__completionNotificationEnabled) { if ((tab as any).__completionNotificationEnabled) {
this.app.observeTabCompletion(tab).subscribe(() => { this.app.observeTabCompletion(tab).subscribe(() => {

View File

@ -1,6 +1,7 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Theme } from './api' import { Theme } from './api'
/** @hidden */
@Injectable() @Injectable()
export class StandardTheme extends Theme { export class StandardTheme extends Theme {
name = 'Standard' name = 'Standard'
@ -8,6 +9,7 @@ export class StandardTheme extends Theme {
terminalBackground = '#222a33' terminalBackground = '#222a33'
} }
/** @hidden */
@Injectable() @Injectable()
export class StandardCompactTheme extends Theme { export class StandardCompactTheme extends Theme {
name = 'Compact' name = 'Compact'
@ -15,6 +17,7 @@ export class StandardCompactTheme extends Theme {
terminalBackground = '#222a33' terminalBackground = '#222a33'
} }
/** @hidden */
@Injectable() @Injectable()
export class PaperTheme extends Theme { export class PaperTheme extends Theme {
name = 'Paper' name = 'Paper'

View File

@ -3,7 +3,7 @@ import { debounceTime, distinctUntilChanged, first, tap, flatMap } from 'rxjs/op
import * as semver from 'semver' import * as semver from 'semver'
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { ConfigService, HostAppService, ElectronService } from 'terminus-core' import { ConfigService, ElectronService } from 'terminus-core'
import { IPluginInfo, PluginManagerService } from '../services/pluginManager.service' import { IPluginInfo, PluginManagerService } from '../services/pluginManager.service'
enum BusyState { Installing, Uninstalling } enum BusyState { Installing, Uninstalling }
@ -25,7 +25,6 @@ export class PluginsSettingsTabComponent {
constructor ( constructor (
private electron: ElectronService, private electron: ElectronService,
private config: ConfigService, private config: ConfigService,
private hostApp: HostAppService,
public pluginManager: PluginManagerService public pluginManager: PluginManagerService
) { ) {
} }
@ -51,7 +50,7 @@ export class PluginsSettingsTabComponent {
} }
openPluginsFolder (): void { openPluginsFolder (): void {
this.hostApp.getShell().openItem(this.pluginManager.userPluginsPath) this.electron.shell.openItem(this.pluginManager.userPluginsPath)
} }
searchAvailable (query: string) { searchAvailable (query: string) {

7
typedoc.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
ignoreCompilerErrors: true,
excludeNotExported: true,
excludePrivate: true,
excludeExternals: true,
mode: 'file'
}

159
yarn.lock
View File

@ -39,6 +39,18 @@
resolved "https://registry.yarnpkg.com/@types/electron-debug/-/electron-debug-1.1.0.tgz#b9203bad33dccc5a4ea180a89a9dbcf1961f4c3c" resolved "https://registry.yarnpkg.com/@types/electron-debug/-/electron-debug-1.1.0.tgz#b9203bad33dccc5a4ea180a89a9dbcf1961f4c3c"
integrity sha1-uSA7rTPczFpOoYComp288ZYfTDw= integrity sha1-uSA7rTPczFpOoYComp288ZYfTDw=
"@types/events@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
"@types/fs-extra@^5.0.3":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b"
integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A==
dependencies:
"@types/node" "*"
"@types/fs-promise@1.0.1": "@types/fs-promise@1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/fs-promise/-/fs-promise-1.0.1.tgz#a77e18c055d7757d44a34c1ed7e8bb505992f783" resolved "https://registry.yarnpkg.com/@types/fs-promise/-/fs-promise-1.0.1.tgz#a77e18c055d7757d44a34c1ed7e8bb505992f783"
@ -47,11 +59,47 @@
"@types/mz" "*" "@types/mz" "*"
"@types/node" "*" "@types/node" "*"
"@types/glob@*":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
dependencies:
"@types/events" "*"
"@types/minimatch" "*"
"@types/node" "*"
"@types/handlebars@^4.0.38":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.1.0.tgz#3fcce9bf88f85fe73dc932240ab3fb682c624850"
integrity sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA==
dependencies:
handlebars "*"
"@types/highlight.js@^9.12.3":
version "9.12.3"
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==
"@types/js-yaml@^3.11.2": "@types/js-yaml@^3.11.2":
version "3.11.2" version "3.11.2"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.11.2.tgz#699ad86054cc20043c30d66a6fcde30bbf5d3d5e" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.11.2.tgz#699ad86054cc20043c30d66a6fcde30bbf5d3d5e"
integrity sha512-JRDtMPEqXrzfuYAdqbxLot1GvAr/QvicIZAnOAigZaj8xVMhuSJTg/xsv9E1TvyL+wujYhRLx9ZsQ0oFOSmwyA== integrity sha512-JRDtMPEqXrzfuYAdqbxLot1GvAr/QvicIZAnOAigZaj8xVMhuSJTg/xsv9E1TvyL+wujYhRLx9ZsQ0oFOSmwyA==
"@types/lodash@^4.14.110":
version "4.14.122"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.122.tgz#3e31394c38cf1e5949fb54c1192cbc406f152c6c"
integrity sha512-9IdED8wU93ty8gP06ninox+42SBSJHp2IAamsSYMUY76mshRTeUsid/gtbl8ovnOwy8im41ib4cxTiIYMXGKew==
"@types/marked@^0.4.0":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.2.tgz#64a89e53ea37f61cc0f3ee1732c555c2dbf6452f"
integrity sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg==
"@types/minimatch@*", "@types/minimatch@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/mz@*": "@types/mz@*":
version "0.0.32" version "0.0.32"
resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.32.tgz#e8248b4e41424c052edc1725dd33650c313a3659" resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.32.tgz#e8248b4e41424c052edc1725dd33650c313a3659"
@ -69,6 +117,14 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.26.tgz#2dec19f1f7981c95cb54bab8f618ecb5dc983d0e" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.26.tgz#2dec19f1f7981c95cb54bab8f618ecb5dc983d0e"
integrity sha512-nMRqS+mL1TOnIJrL6LKJcNZPB8V3eTfRo9FQA2b5gDvrHurC8XbSA86KNe0dShlEL7ReWJv/OU9NL7Z0dnqWTg== integrity sha512-nMRqS+mL1TOnIJrL6LKJcNZPB8V3eTfRo9FQA2b5gDvrHurC8XbSA86KNe0dShlEL7ReWJv/OU9NL7Z0dnqWTg==
"@types/shelljs@^0.8.0":
version "0.8.3"
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.3.tgz#f713f312dbae49ab5025290007e71ea32998e9a9"
integrity sha512-miY41hqc5SkRlsZDod3heDa4OS9xv8G77EMBQuSpqq86HBn66l7F+f8y9YKm+1PIuwC8QEZVwN8YxOOG7Y67fA==
dependencies:
"@types/glob" "*"
"@types/node" "*"
"@types/webpack-env@1.13.0": "@types/webpack-env@1.13.0":
version "1.13.0" version "1.13.0"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.0.tgz#3044381647e11ee973c5af2e925323930f691d80" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.0.tgz#3044381647e11ee973c5af2e925323930f691d80"
@ -649,6 +705,13 @@ async@^2.0.0:
dependencies: dependencies:
lodash "^4.17.10" lodash "^4.17.10"
async@^2.5.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
dependencies:
lodash "^4.17.11"
asynckit@^0.4.0: asynckit@^0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@ -3051,6 +3114,17 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
handlebars@*, handlebars@^4.0.6:
version "4.1.0"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a"
integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==
dependencies:
async "^2.5.0"
optimist "^0.6.1"
source-map "^0.6.1"
optionalDependencies:
uglify-js "^3.1.4"
har-schema@^1.0.5: har-schema@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
@ -3181,6 +3255,11 @@ he@1.1.x:
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
highlight.js@^9.13.1:
version "9.15.6"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.6.tgz#72d4d8d779ec066af9a17cb14360c3def0aa57c4"
integrity sha512-zozTAWM1D6sozHo8kqhfYgsac+B+q0PmsjXeyDrYIHHcBN0zTVT66+s2GW1GZv7DbyaROdLXKdabwS/WqPyIdQ==
hmac-drbg@^1.0.0: hmac-drbg@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -4135,7 +4214,7 @@ lodash.without@~4.4.0:
resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=
lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
version "4.17.11" version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@ -4242,6 +4321,11 @@ map-visit@^1.0.0:
dependencies: dependencies:
object-visit "^1.0.0" object-visit "^1.0.0"
marked@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.4.0.tgz#9ad2c2a7a1791f10a852e0112f77b571dce10c66"
integrity sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw==
math-expression-evaluator@^1.2.14: math-expression-evaluator@^1.2.14:
version "1.2.17" version "1.2.17"
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
@ -4388,6 +4472,11 @@ minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
minipass@^2.2.1, minipass@^2.3.3: minipass@^2.2.1, minipass@^2.3.3:
version "2.3.4" version "2.3.4"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957"
@ -5066,6 +5155,14 @@ opener@~1.4.3:
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg= integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
dependencies:
minimist "~0.0.1"
wordwrap "~0.0.2"
ora@^1.2.0: ora@^1.2.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/ora/-/ora-1.4.0.tgz#884458215b3a5d4097592285f93321bb7a79e2e5" resolved "https://registry.yarnpkg.com/ora/-/ora-1.4.0.tgz#884458215b3a5d4097592285f93321bb7a79e2e5"
@ -5709,6 +5806,11 @@ progress-stream@^1.1.0:
speedometer "~0.1.2" speedometer "~0.1.2"
through2 "~0.2.3" through2 "~0.2.3"
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-inflight@^1.0.1, promise-inflight@~1.0.1: promise-inflight@^1.0.1, promise-inflight@~1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
@ -6671,6 +6773,15 @@ shelljs@0.7.7:
interpret "^1.0.0" interpret "^1.0.0"
rechoir "^0.6.2" rechoir "^0.6.2"
shelljs@^0.8.2:
version "0.8.3"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097"
integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==
dependencies:
glob "^7.0.0"
interpret "^1.0.0"
rechoir "^0.6.2"
signal-exit@^3.0.0, signal-exit@^3.0.2: signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
@ -7472,6 +7583,39 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typedoc-default-themes@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz#6dc2433e78ed8bea8e887a3acde2f31785bd6227"
integrity sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic=
typedoc@^0.14.2:
version "0.14.2"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.14.2.tgz#769f457f4f9e4bdb8b5f3b177c86b6a31d8c3dc3"
integrity sha512-aEbgJXV8/KqaVhcedT7xG6d2r+mOvB5ep3eIz1KuB5sc4fDYXcepEEMdU7XSqLFO5hVPu0nllHi1QxX2h/QlpQ==
dependencies:
"@types/fs-extra" "^5.0.3"
"@types/handlebars" "^4.0.38"
"@types/highlight.js" "^9.12.3"
"@types/lodash" "^4.14.110"
"@types/marked" "^0.4.0"
"@types/minimatch" "3.0.3"
"@types/shelljs" "^0.8.0"
fs-extra "^7.0.0"
handlebars "^4.0.6"
highlight.js "^9.13.1"
lodash "^4.17.10"
marked "^0.4.0"
minimatch "^3.0.0"
progress "^2.0.0"
shelljs "^0.8.2"
typedoc-default-themes "^0.5.0"
typescript "3.2.x"
typescript@3.2.x:
version "3.2.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d"
integrity sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==
typescript@^3.1.3: typescript@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.3.tgz#01b70247a6d3c2467f70c45795ef5ea18ce191d5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.3.tgz#01b70247a6d3c2467f70c45795ef5ea18ce191d5"
@ -7495,6 +7639,14 @@ uglify-js@^2.6.1:
optionalDependencies: optionalDependencies:
uglify-to-browserify "~1.0.0" uglify-to-browserify "~1.0.0"
uglify-js@^3.1.4:
version "3.4.9"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
dependencies:
commander "~2.17.1"
source-map "~0.6.1"
uglify-to-browserify@~1.0.0: uglify-to-browserify@~1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
@ -7881,6 +8033,11 @@ wordwrap@0.0.2:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=
wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
worker-farm@^1.5.2: worker-farm@^1.5.2:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0"