├── index.js ├── assets └── screenshot.png ├── lib ├── assets │ ├── electron.png │ ├── notification.html │ └── notification.css ├── app.js ├── behaviors │ ├── swipeRightBehavior.js │ └── clickBehavior.js ├── notification.js ├── notificationView.js └── notifier.js ├── .gitignore ├── playbook ├── main.js ├── assets │ ├── run.css │ ├── run.js │ └── vendor │ │ ├── codemirror.css │ │ └── codemirror-js.js └── playbook.html ├── package.json ├── LICENSE └── readme.md /index.js: -------------------------------------------------------------------------------- 1 | const Notifier = require('./lib/notifier') 2 | 3 | module.exports = new Notifier() 4 | -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayleedev/electron-notifications/HEAD/assets/screenshot.png -------------------------------------------------------------------------------- /lib/assets/electron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayleedev/electron-notifications/HEAD/lib/assets/electron.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | Thumbs.db 5 | *.autogenerated 6 | /build/ 7 | /releases/ 8 | /tmp/ 9 | .settings 10 | .project 11 | -------------------------------------------------------------------------------- /lib/app.js: -------------------------------------------------------------------------------- 1 | const Notification = require('../notification') 2 | 3 | const { ipcRenderer } = require('electron') 4 | 5 | ipcRenderer.on('setup', (event, title, options) => { 6 | new Notification(title, options) 7 | }) 8 | -------------------------------------------------------------------------------- /playbook/main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require('electron') 2 | 3 | let mainWindow = null 4 | 5 | app.on('ready', () => { 6 | mainWindow = new BrowserWindow({width: 800, height: 600}) 7 | mainWindow.loadURL('file://' + __dirname + '/playbook.html') 8 | mainWindow.on('closed', () => { 9 | mainWindow = null 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /playbook/assets/run.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #eeeeee; 3 | font-size: 15px; 4 | font-family: 'Lora', serif; 5 | } 6 | 7 | h2 { 8 | font-family: 'Istok Web', sans-serif; 9 | } 10 | 11 | .sample { 12 | display: flex; 13 | padding: 20px 0; 14 | } 15 | 16 | .sample div.talk { 17 | flex: 1; 18 | } 19 | 20 | .sample div.code { 21 | flex: 2; 22 | height: 200px; 23 | padding: 15px; 24 | } 25 | 26 | .CodeMirror { 27 | height: 200px; 28 | font-size: 14px; 29 | } 30 | 31 | #stack-num { 32 | width : 50px; 33 | } 34 | 35 | #stack-delay { 36 | width : 50px; 37 | } -------------------------------------------------------------------------------- /lib/assets/notification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Notification 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 | 15 |
16 |

17 |

18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/behaviors/swipeRightBehavior.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | const EventEmitter = require('events') 3 | 4 | const { remote } = electron 5 | 6 | class SwipeRightBehavior extends EventEmitter { 7 | constructor () { 8 | super() 9 | this.mainWindow = remote.getCurrentWindow() 10 | this.xLeader = this.mainWindow.getPosition()[0] 11 | this.xFollower = this.xLeader 12 | this.mainWindow.on('move', this.move.bind(this)) 13 | } 14 | 15 | move () { 16 | this.xFollower = this.xLeader 17 | this.xLeader = this.mainWindow.getPosition()[0] 18 | if (this.xFollower < this.xLeader) { 19 | this.emit('behavior', 'swipedRight') 20 | } 21 | } 22 | } 23 | 24 | module.exports = SwipeRightBehavior 25 | -------------------------------------------------------------------------------- /lib/notification.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | const NotificationView = require('./notificationView') 3 | const ClickBehavior = require('./behaviors/clickBehavior') 4 | const SwipeRightBehavior = require('./behaviors/swipeRightBehavior') 5 | 6 | const { remote } = electron 7 | 8 | class Notification { 9 | constructor (title, options) { 10 | this.mainWindow = remote.getCurrentWindow() 11 | this.view = new NotificationView(title, options) 12 | this.view.render() 13 | this.addBehavior(ClickBehavior) 14 | this.addBehavior(SwipeRightBehavior) 15 | } 16 | 17 | addBehavior (Klass) { 18 | const item = new Klass(this.view.element) 19 | item.on('behavior', (eventName) => { 20 | this.mainWindow.emit(eventName) 21 | }) 22 | } 23 | } 24 | 25 | module.exports = Notification 26 | -------------------------------------------------------------------------------- /lib/behaviors/clickBehavior.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | const EventEmitter = require('events') 3 | 4 | const { remote } = electron 5 | 6 | class ClickBehavior extends EventEmitter { 7 | constructor (element) { 8 | super() 9 | this.mainWindow = remote.getCurrentWindow() 10 | this.mainWindow.on('move', this.move.bind(this)) 11 | this.clicks = this.moves = 0 12 | element.addEventListener('click', this.click.bind(this)) 13 | } 14 | 15 | checkClick () { 16 | if (this.clicks !== this.moves) { 17 | this.clicks = this.moves = 0 18 | this.emit('behavior', 'clicked') 19 | } 20 | } 21 | 22 | move () { 23 | this.moves++ 24 | } 25 | 26 | click (event) { 27 | if (event.target.tagName === 'A') return 28 | this.clicks++ 29 | clearTimeout(this.timeout) 30 | this.timeout = setTimeout(this.checkClick.bind(this), 150) 31 | } 32 | } 33 | 34 | module.exports = ClickBehavior 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-notifications", 3 | "version": "1.0.0", 4 | "description": "A node module for sending notifications in electron applications", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npm run playbook", 8 | "playbook": "electron playbook/main.js", 9 | "lint": "standard" 10 | }, 11 | "keywords": [ 12 | "electron", 13 | "notification", 14 | "notification center", 15 | "toaster", 16 | "growl", 17 | "notify-send", 18 | "terminal-notifier", 19 | "notify" 20 | ], 21 | "repository": "git@github.com:blainesch/electron-notifications.git", 22 | "author": "Blaine Schmeisser", 23 | "license": "MIT", 24 | "standard": { 25 | "globals": [ 26 | "CodeMirror" 27 | ], 28 | "ignore": [ 29 | "playbook/assets/vendor" 30 | ] 31 | }, 32 | "devDependencies": { 33 | "electron": "^1.3.4", 34 | "standard": "^8.0.0" 35 | }, 36 | "dependencies": { 37 | "electron-is": "^2.3.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Blaine Schmeisser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /playbook/assets/run.js: -------------------------------------------------------------------------------- 1 | const textareas = {} 2 | 3 | document.querySelectorAll('textarea').forEach((snippet) => { 4 | textareas[snippet.id] = CodeMirror.fromTextArea(snippet, { 5 | mode: 'javascript', 6 | lineNumbers: true 7 | }) 8 | }) 9 | 10 | document.querySelectorAll('button').forEach((button) => { 11 | button.addEventListener('click', function () { 12 | const id = this.getAttribute('for') 13 | const item = textareas[id] 14 | item.save() 15 | const el = item.getTextArea() 16 | var code = el.value 17 | let replacedCode; 18 | if(id=="stacking") { 19 | var num = parseInt(window.document.getElementById('stack-num').value); 20 | var stackDelay = parseInt(window.document.getElementById('stack-delay').value); 21 | console.log("stack delay = "+stackDelay); 22 | var whenToEval = 0; 23 | if(!num) { 24 | num = 1; 25 | } 26 | var originalCode = code; 27 | for(var i = 0 ; i < num ; i++) { 28 | code = originalCode.replace('{i}', i+1); 29 | replacedCode = code.replace('electron-notifications', '../index.js'); 30 | setTimeout(eval, whenToEval,replacedCode); 31 | whenToEval += stackDelay; 32 | console.log("will evaluate next in "+whenToEval); 33 | } 34 | } else { 35 | replacedCode = code.replace('electron-notifications', '../index.js') 36 | eval(replacedCode) 37 | } 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /lib/notificationView.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | 3 | const { remote } = electron 4 | 5 | class NotificationView { 6 | constructor (title, options) { 7 | this.element = document.getElementById('notification') 8 | this.iconEl = document.getElementById('icon') 9 | this.titleEl = document.getElementById('title') 10 | this.messageEl = document.getElementById('message') 11 | this.buttonsEl = document.getElementById('buttons') 12 | this.title = title 13 | this.options = options 14 | } 15 | 16 | render () { 17 | this.titleEl.innerHTML = this.title 18 | this.iconEl.src = this.options.icon || 'electron.png' 19 | 20 | if (this.options.message) { 21 | this.messageEl.innerHTML = this.options.message 22 | } else { 23 | const parent = this.messageEl.parentElement 24 | parent.classList.add('onlyTitle') 25 | parent.removeChild(this.messageEl) 26 | } 27 | 28 | this.setupButtons() 29 | this.decorateClasses() 30 | } 31 | 32 | setupButtons () { 33 | this.buttons().forEach((actionName, buttonIndex) => { 34 | const link = document.createElement('a') 35 | link.href = '#' 36 | link.innerHTML = actionName 37 | link.addEventListener('click', (event) => { 38 | const mainWindow = remote.getCurrentWindow() 39 | mainWindow.emit('buttonClicked', event.target.innerHTML, buttonIndex, this.options) 40 | }) 41 | this.buttonsEl.appendChild(link) 42 | }) 43 | } 44 | 45 | decorateClasses () { 46 | const buttonLength = this.buttons().length 47 | 48 | if (buttonLength > 0) { 49 | this.element.classList.add('actions') 50 | } 51 | 52 | if (this.options.vertical) { 53 | this.element.classList.add('vertical') 54 | } else { 55 | this.element.classList.add('horizontal') 56 | } 57 | 58 | if (buttonLength >= 2) { 59 | this.element.classList.add('double') 60 | } else { 61 | this.element.classList.add('single') 62 | } 63 | 64 | if (this.options.flat) { 65 | this.element.classList.add('flat') 66 | this.iconEl.classList.add('flat') 67 | this.titleEl.classList.add('flat') 68 | this.messageEl.classList.add('flat') 69 | this.buttonsEl.classList.add('flat') 70 | } 71 | } 72 | 73 | buttons () { 74 | return (this.options.buttons || []).slice(0, 2) 75 | } 76 | } 77 | 78 | module.exports = NotificationView 79 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Electron Notifications 2 | 3 | A node module for sending notifications in electron applications. 4 | 5 | ![screenshot](assets/screenshot.png) 6 | 7 | ## Quick Usage 8 | 9 | ~~~ javascript 10 | const notifier = require('electron-notifications') 11 | 12 | // Just title 13 | notifier.notify('Calendar') 14 | 15 | // Full Options 16 | notifier.notify('Calendar', { 17 | message: 'Event begins in 10 minutes', 18 | icon: 'http://cl.ly/J49B/3951818241085781941.png', 19 | buttons: ['Dismiss', 'Snooze'], 20 | }) 21 | ~~~ 22 | 23 | ## Installation 24 | 25 | ~~~ 26 | npm install --save electron-notifications 27 | ~~~ 28 | 29 | ## Playbook 30 | 31 | If you'd like to see this in action you can run the playbook and try out live 32 | examples and edit the code in place. 33 | 34 | ~~~ 35 | git clone git@github.com:blainesch/electron-notifications.git 36 | npm run playbook 37 | ~~~ 38 | 39 | ## Introduction 40 | 41 | When you create a new notification, your notification is queued, since we only 42 | display one at a time. Each notification is a [BrowserWindow](browserwindow) 43 | instance, so it's completely cross platform. 44 | 45 | ## Options 46 | 47 | All options are optional. 48 | 49 | * `message`: A message to display under the title. 50 | * `icon`: The absolute URL of a icon displayed to the left of the text. 51 | * `buttons`: One or two buttons to display on the right of the notification. 52 | * `vertical`: Boolean (default: false) that specifies that the buttons should be stacked vertically. 53 | * `duration`: Integer duration in milliseconds (default: 4000) to show the notification. 54 | * `flat`: Boolean (default: false) that specifies to use a flat button style notification. 55 | 56 | ## Events 57 | 58 | In addition to the [events provided by electron](events) you also have access to 59 | the following 3 additional events. 60 | 61 | 62 | ### Clicked 63 | 64 | When the notification was clicked, but not dragged. This usually does the 65 | default action, or closes the notification. 66 | 67 | ~~~ javascript 68 | const notification = notifier.notify('Calendar') 69 | 70 | notification.on('clicked', () => { 71 | notification.close() 72 | }) 73 | ~~~ 74 | 75 | ### Swiped Right 76 | 77 | When the notification has been swiped to the right. This usually indicates that 78 | the user wants to dismiss the notification. 79 | 80 | ~~~ javascript 81 | const notification = notifier.notify('Calendar') 82 | 83 | notification.on('swipedRight', () => { 84 | notification.close() 85 | }) 86 | ~~~ 87 | 88 | ### Button Clicked 89 | 90 | When any one of the buttons are clicked, it'll trigger a `buttonClicked` event, 91 | and pass the text, button index and options to the handler. 92 | 93 | ~~~ javascript 94 | const notification = notifier.notify('Calendar', { 95 | buttons: ['Dismiss', 'Snooze'], 96 | url: "http://google.com" 97 | }) 98 | 99 | notification.on('buttonClicked', (text, buttonIndex, options) => { 100 | if (text === 'Snooze') { 101 | // Snooze! 102 | } else if(buttonIndex === 1) { 103 | //open options.url 104 | } 105 | notification.close() 106 | }) 107 | ~~~ 108 | 109 | [events]: https://github.com/electron/electron/blob/master/docs/api/browser-window.md#events 110 | [browserwindow]: https://github.com/electron/electron/blob/master/docs/api/browser-window.md 111 | -------------------------------------------------------------------------------- /lib/assets/notification.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | overflow: hidden; 3 | background: rgba(0, 0, 0, 0); 4 | color: #fff; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | a { 10 | color: #fff; 11 | } 12 | 13 | #notification { 14 | height: 80px; 15 | width: 400px; 16 | margin: 0; 17 | position: relative; 18 | font-family: Helvetica, Arial, sans-serif; 19 | background-image: linear-gradient(rgba(120, 119, 119, 0.95), rgba(75, 74, 74, 0.95)); 20 | border: 1px solid rgba(255, 255, 255, 0.5); 21 | box-sizing: border-box; 22 | -webkit-app-region: drag; 23 | -webkit-user-select: none; 24 | } 25 | #notification.vertical.single { 26 | height: 120px; 27 | } 28 | #notification.vertical.double { 29 | height: 160px; 30 | } 31 | #notification .icon { 32 | width: 90px; 33 | height: 80px; 34 | float: left; 35 | } 36 | #notification .icon img { 37 | padding: 10px 15px 0 15px; 38 | width: 60px; 39 | } 40 | #notification.actions { 41 | -webkit-app-region: no-drag; 42 | } 43 | #notification, 44 | #notification .icon, 45 | #notification .icon img, 46 | #notification .message, 47 | #notification .message:before, 48 | #notification .message h2, 49 | #notification .message p { 50 | -webkit-app-region: drag; 51 | } 52 | #notification.actions .message { 53 | width: 205px; 54 | } 55 | #notification.vertical.actions .message { 56 | width: 308px; 57 | } 58 | #notification .message { 59 | height: 80px; 60 | width: 310px; 61 | float: left; 62 | text-overflow: clip; 63 | white-space: nowrap; 64 | overflow: hidden; 65 | position: relative; 66 | } 67 | #notification .message:before { 68 | content: ''; 69 | width: 100%; 70 | height: 100%; 71 | position: absolute; 72 | left: 0; 73 | top: 0; 74 | } 75 | #notification .message.onlyTitle h2 { 76 | padding: 30px; 77 | } 78 | #notification .message h2 { 79 | padding: 20px 0 0 0px; 80 | margin: 0 0 5px; 81 | font: bold 18px Myriad, Helvetica, Arial, sans-serif; 82 | } 83 | #notification .message p { 84 | padding: 0; 85 | margin: 0; 86 | font: bold 13px Myriad, Helvetica, Arial, sans-serif; 87 | font-weight: normal; 88 | } 89 | #notification #buttons { 90 | float: right; 91 | position: relative; 92 | } 93 | #notification.horizontal #buttons { 94 | border-left: 1px solid black; 95 | box-shadow: inset 1px 0 rgba(255, 255, 255, 0.4); 96 | } 97 | #notification.vertical #buttons { 98 | width: 100%; 99 | position: absolute; 100 | top: 80px; 101 | } 102 | #notification.vertical.single #buttons a { 103 | height: 40px; 104 | line-height: 40px; 105 | } 106 | #notification.vertical #buttons a { 107 | padding-left: 20px; 108 | text-align: left; 109 | width: 100%; 110 | height: 40px; 111 | line-height: 40px; 112 | border-top: 1px solid black; 113 | box-shadow: inset 0 1px rgba(255, 255, 255, 0.4); 114 | } 115 | #notification.double #buttons a { 116 | height: 40px; 117 | line-height: 40px; 118 | } 119 | #notification.double.horizontal #buttons a:nth-child(2) { 120 | border-top: 1px solid black; 121 | box-shadow: inset 0 1px rgba(255, 255, 255, 0.4); 122 | } 123 | #notification.double #buttons a:nth-child(2) { 124 | top: 40px; 125 | } 126 | #notification.single #buttons a { 127 | height: 80px; 128 | line-height: 80px; 129 | } 130 | #notification #buttons a { 131 | box-sizing: border-box; 132 | top: 0; 133 | display: block; 134 | width: 100px; 135 | font: 13px Helvetica, Arial, sans-serif; 136 | text-align: center; 137 | text-decoration: none; 138 | right: 0; 139 | } 140 | #notification #buttons a:active { 141 | background-color: rgba(75, 74, 74, 0.95); 142 | } 143 | -------------------------------------------------------------------------------- /playbook/playbook.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Notification Playbook 6 | 7 | 8 |
9 |

Electron Notification Playbook

10 |
11 |
12 |

Simple Example

13 |

The minimum requirements.

14 | 15 |
16 |
17 | 21 |
22 |
23 |
24 |
25 |

Full options

26 |

Shows you the full array of options you can use.

27 | 28 |
29 |
30 | 39 |
40 |
41 |
42 |
43 |

Stacking

44 |

Shows you the full array of options you can use with stacking

45 |

{i} will be replaced with the count

46 | # of notifications 47 |
48 | delay bewteen (ms) 49 |
50 | 51 |
52 |
53 | 62 |
63 |
64 |
65 |
66 |

Vertical Buttons

67 |

If you prefer to use the vertical buttons style.

68 | 69 |
70 |
71 | 80 |
81 |
82 |
83 |
84 |

Click event

85 |

Click on the notifiation to see the alert.

86 | 87 |
88 |
89 | 99 |
100 |
101 |
102 |
103 |

Swipe right event

104 |

Swipe the notification right to trigger the alert.

105 | 106 |
107 |
108 | 118 |
119 |
120 |
121 |
122 |

Click button

123 |

Click a button to see the action

124 | 125 |
126 |
127 | 139 |
140 |
141 |
142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /lib/notifier.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | const is = require('electron-is'); 3 | 4 | class Notifier { 5 | constructor () { 6 | this.activeNotifications = []; 7 | this.notificationHeight = 80; 8 | this.queue = []; 9 | this.startBarSizeEstimate = 90; 10 | if (process.type === 'renderer') { 11 | this.BrowserWindow = electron.remote.BrowserWindow; 12 | } else { 13 | this.BrowserWindow = electron.BrowserWindow; 14 | } 15 | } 16 | 17 | configure(options) { 18 | if(options.hasOwnProperty('stackGap')) { 19 | this.stackGap = options.stackGap; 20 | } 21 | } 22 | 23 | notify (title, data) { 24 | const isWindows = process.platform === 'win32' 25 | const options = Object.assign({}, data); 26 | const size = electron.screen.getPrimaryDisplay().workAreaSize; 27 | let verticalSpace = 0; 28 | if (options.vertical && options.buttons && options.buttons.length) { 29 | verticalSpace = Math.min(options.buttons.length * 40, 80); 30 | } else { 31 | options.vertical = false; 32 | } 33 | options.stackGap = this.stackGap; 34 | let windowOptions = { 35 | width: 400, 36 | height: this.notificationHeight + verticalSpace, 37 | x: size.width - 400, 38 | y: 0, /* this is because we set the poper position when we pop it from the queue */ 39 | frame: false, 40 | resizable: false, 41 | alwaysOnTop: true, 42 | skipTaskbar: true, 43 | webPreferences: { 44 | devTools: false, 45 | nodeIntegration: true 46 | }, 47 | focusable: false, 48 | }; 49 | if (!is.macOS()) { 50 | //this causes close/resize buttons to show on macOS 51 | windowOptions.titleBarStyle = 'hidden'; 52 | } 53 | const notificationWindow = new this.BrowserWindow(windowOptions); 54 | this.queue.push({ 55 | window:notificationWindow, 56 | title:title, 57 | options:options 58 | }); 59 | this.showNext(); 60 | return notificationWindow; 61 | } 62 | 63 | showNext () { 64 | if (this.queue.length === 0) { 65 | return; 66 | } 67 | const size = electron.screen.getPrimaryDisplay().workAreaSize; 68 | var availableHeight = size.height - (is.windows()?this.startBarSizeEstimate:0); 69 | for(var j = 0; j < this.activeNotifications.length ; j++) { 70 | availableHeight -= this.activeNotifications[j].window.getBounds().height; 71 | } 72 | if(availableHeight < this.queue[0].window.getBounds().height) { 73 | return; 74 | } 75 | const notification = this.queue.shift(); 76 | const title = notification.title; 77 | const options = notification.options; 78 | let notificationWindow = notification.window; 79 | 80 | var notificationY = 0; 81 | 82 | for(var i = 0; i < this.activeNotifications.length ; i++) { 83 | var item = this.activeNotifications[i]; 84 | notificationY += item.window.getBounds().height + 10 85 | } 86 | 87 | this.activeNotifications.push(notification); 88 | 89 | notificationWindow.loadURL('file://' + __dirname + '/assets/notification.html'); 90 | 91 | notificationWindow.webContents.on('did-finish-load', () => { 92 | notificationWindow.show(); 93 | notificationWindow.webContents.send('setup', title, options); 94 | }) 95 | const timeout = setTimeout(() => { 96 | //console.log("closing notification window!") 97 | //console.log("Is notification window gone? ", notificationWindow.isDestroyed()) 98 | if (!notificationWindow.isDestroyed()) { 99 | notificationWindow.close() 100 | } 101 | }, options.duration || 4000); 102 | 103 | notificationWindow.setPosition( 104 | notificationWindow.getPosition()[0], 105 | is.windows()?size.height-this.startBarSizeEstimate-notificationY:notificationY, 106 | true 107 | ); 108 | 109 | const currentWindow = electron.remote && electron.remote.getCurrentWindow() 110 | if (notificationWindow) { 111 | notificationWindow.on('close', () => { 112 | this.nextY = 0; 113 | var loc = this.activeNotifications.indexOf(notification); 114 | if(loc > -1) { 115 | this.activeNotifications = this.activeNotifications.filter(function(item){ 116 | return item.window != this.window; 117 | }.bind(notification)); 118 | } 119 | if (notificationWindow) { 120 | notificationWindow.removeAllListeners(); 121 | } 122 | for(var i = 0; i < this.activeNotifications.length ; i++) { 123 | var item = this.activeNotifications[i]; 124 | var canMove = true; 125 | try { 126 | item.window.getPosition(); 127 | } catch(e) { 128 | canMove = false; 129 | } 130 | if(canMove) { 131 | console.log("window at index "+[1]+" is moving to position "+this.nextY); 132 | const size = electron.screen.getPrimaryDisplay().workAreaSize 133 | // TODO - do a pretty slide up/down to collapse list 134 | item.window.setPosition( 135 | item.window.getPosition()[0], 136 | is.windows()?size.height-this.startBarSizeEstimate-this.nextY:this.nextY, 137 | true /* TODO : this is electron "animate" param - it's not working on windows */ 138 | ); 139 | var itemHeight = item.window.getBounds().height; 140 | this.nextY += itemHeight; 141 | console.log(item); 142 | } 143 | } 144 | if(this.queue.length) { 145 | this.showNext(); 146 | 147 | } 148 | }); 149 | } 150 | this.showNext(); 151 | notificationWindow.on('closed', () => { 152 | clearTimeout(timeout); 153 | notificationWindow = null; 154 | }); 155 | } 156 | } 157 | 158 | module.exports = Notifier; 159 | -------------------------------------------------------------------------------- /playbook/assets/vendor/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | border-right: none; 47 | width: 0; 48 | } 49 | /* Shown when moving in bi-directional text */ 50 | .CodeMirror div.CodeMirror-secondarycursor { 51 | border-left: 1px solid silver; 52 | } 53 | .cm-fat-cursor .CodeMirror-cursor { 54 | width: auto; 55 | border: 0 !important; 56 | background: #7e7; 57 | } 58 | .cm-fat-cursor div.CodeMirror-cursors { 59 | z-index: 1; 60 | } 61 | 62 | .cm-animate-fat-cursor { 63 | width: auto; 64 | border: 0; 65 | -webkit-animation: blink 1.06s steps(1) infinite; 66 | -moz-animation: blink 1.06s steps(1) infinite; 67 | animation: blink 1.06s steps(1) infinite; 68 | background-color: #7e7; 69 | } 70 | @-moz-keyframes blink { 71 | 0% {} 72 | 50% { background-color: transparent; } 73 | 100% {} 74 | } 75 | @-webkit-keyframes blink { 76 | 0% {} 77 | 50% { background-color: transparent; } 78 | 100% {} 79 | } 80 | @keyframes blink { 81 | 0% {} 82 | 50% { background-color: transparent; } 83 | 100% {} 84 | } 85 | 86 | /* Can style cursor different in overwrite (non-insert) mode */ 87 | .CodeMirror-overwrite .CodeMirror-cursor {} 88 | 89 | .cm-tab { display: inline-block; text-decoration: inherit; } 90 | 91 | .CodeMirror-rulers { 92 | position: absolute; 93 | left: 0; right: 0; top: -50px; bottom: -20px; 94 | overflow: hidden; 95 | } 96 | .CodeMirror-ruler { 97 | border-left: 1px solid #ccc; 98 | top: 0; bottom: 0; 99 | position: absolute; 100 | } 101 | 102 | /* DEFAULT THEME */ 103 | 104 | .cm-s-default .cm-header {color: blue;} 105 | .cm-s-default .cm-quote {color: #090;} 106 | .cm-negative {color: #d44;} 107 | .cm-positive {color: #292;} 108 | .cm-header, .cm-strong {font-weight: bold;} 109 | .cm-em {font-style: italic;} 110 | .cm-link {text-decoration: underline;} 111 | .cm-strikethrough {text-decoration: line-through;} 112 | 113 | .cm-s-default .cm-keyword {color: #708;} 114 | .cm-s-default .cm-atom {color: #219;} 115 | .cm-s-default .cm-number {color: #164;} 116 | .cm-s-default .cm-def {color: #00f;} 117 | .cm-s-default .cm-variable, 118 | .cm-s-default .cm-punctuation, 119 | .cm-s-default .cm-property, 120 | .cm-s-default .cm-operator {} 121 | .cm-s-default .cm-variable-2 {color: #05a;} 122 | .cm-s-default .cm-variable-3 {color: #085;} 123 | .cm-s-default .cm-comment {color: #a50;} 124 | .cm-s-default .cm-string {color: #a11;} 125 | .cm-s-default .cm-string-2 {color: #f50;} 126 | .cm-s-default .cm-meta {color: #555;} 127 | .cm-s-default .cm-qualifier {color: #555;} 128 | .cm-s-default .cm-builtin {color: #30a;} 129 | .cm-s-default .cm-bracket {color: #997;} 130 | .cm-s-default .cm-tag {color: #170;} 131 | .cm-s-default .cm-attribute {color: #00c;} 132 | .cm-s-default .cm-hr {color: #999;} 133 | .cm-s-default .cm-link {color: #00c;} 134 | 135 | .cm-s-default .cm-error {color: #f00;} 136 | .cm-invalidchar {color: #f00;} 137 | 138 | .CodeMirror-composing { border-bottom: 2px solid; } 139 | 140 | /* Default styles for common addons */ 141 | 142 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 143 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 144 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 145 | .CodeMirror-activeline-background {background: #e8f2ff;} 146 | 147 | /* STOP */ 148 | 149 | /* The rest of this file contains styles related to the mechanics of 150 | the editor. You probably shouldn't touch them. */ 151 | 152 | .CodeMirror { 153 | position: relative; 154 | overflow: hidden; 155 | background: white; 156 | } 157 | 158 | .CodeMirror-scroll { 159 | overflow: scroll !important; /* Things will break if this is overridden */ 160 | /* 30px is the magic margin used to hide the element's real scrollbars */ 161 | /* See overflow: hidden in .CodeMirror */ 162 | margin-bottom: -30px; margin-right: -30px; 163 | padding-bottom: 30px; 164 | height: 100%; 165 | outline: none; /* Prevent dragging from highlighting the element */ 166 | position: relative; 167 | } 168 | .CodeMirror-sizer { 169 | position: relative; 170 | border-right: 30px solid transparent; 171 | } 172 | 173 | /* The fake, visible scrollbars. Used to force redraw during scrolling 174 | before actual scrolling happens, thus preventing shaking and 175 | flickering artifacts. */ 176 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 177 | position: absolute; 178 | z-index: 6; 179 | display: none; 180 | } 181 | .CodeMirror-vscrollbar { 182 | right: 0; top: 0; 183 | overflow-x: hidden; 184 | overflow-y: scroll; 185 | } 186 | .CodeMirror-hscrollbar { 187 | bottom: 0; left: 0; 188 | overflow-y: hidden; 189 | overflow-x: scroll; 190 | } 191 | .CodeMirror-scrollbar-filler { 192 | right: 0; bottom: 0; 193 | } 194 | .CodeMirror-gutter-filler { 195 | left: 0; bottom: 0; 196 | } 197 | 198 | .CodeMirror-gutters { 199 | position: absolute; left: 0; top: 0; 200 | min-height: 100%; 201 | z-index: 3; 202 | } 203 | .CodeMirror-gutter { 204 | white-space: normal; 205 | height: 100%; 206 | display: inline-block; 207 | vertical-align: top; 208 | margin-bottom: -30px; 209 | /* Hack to make IE7 behave */ 210 | *zoom:1; 211 | *display:inline; 212 | } 213 | .CodeMirror-gutter-wrapper { 214 | position: absolute; 215 | z-index: 4; 216 | background: none !important; 217 | border: none !important; 218 | } 219 | .CodeMirror-gutter-background { 220 | position: absolute; 221 | top: 0; bottom: 0; 222 | z-index: 4; 223 | } 224 | .CodeMirror-gutter-elt { 225 | position: absolute; 226 | cursor: default; 227 | z-index: 4; 228 | } 229 | .CodeMirror-gutter-wrapper { 230 | -webkit-user-select: none; 231 | -moz-user-select: none; 232 | user-select: none; 233 | } 234 | 235 | .CodeMirror-lines { 236 | cursor: text; 237 | min-height: 1px; /* prevents collapsing before first draw */ 238 | } 239 | .CodeMirror pre { 240 | /* Reset some styles that the rest of the page might have set */ 241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 242 | border-width: 0; 243 | background: transparent; 244 | font-family: inherit; 245 | font-size: inherit; 246 | margin: 0; 247 | white-space: pre; 248 | word-wrap: normal; 249 | line-height: inherit; 250 | color: inherit; 251 | z-index: 2; 252 | position: relative; 253 | overflow: visible; 254 | -webkit-tap-highlight-color: transparent; 255 | -webkit-font-variant-ligatures: none; 256 | font-variant-ligatures: none; 257 | } 258 | .CodeMirror-wrap pre { 259 | word-wrap: break-word; 260 | white-space: pre-wrap; 261 | word-break: normal; 262 | } 263 | 264 | .CodeMirror-linebackground { 265 | position: absolute; 266 | left: 0; right: 0; top: 0; bottom: 0; 267 | z-index: 0; 268 | } 269 | 270 | .CodeMirror-linewidget { 271 | position: relative; 272 | z-index: 2; 273 | overflow: auto; 274 | } 275 | 276 | .CodeMirror-widget {} 277 | 278 | .CodeMirror-code { 279 | outline: none; 280 | } 281 | 282 | /* Force content-box sizing for the elements where we expect it */ 283 | .CodeMirror-scroll, 284 | .CodeMirror-sizer, 285 | .CodeMirror-gutter, 286 | .CodeMirror-gutters, 287 | .CodeMirror-linenumber { 288 | -moz-box-sizing: content-box; 289 | box-sizing: content-box; 290 | } 291 | 292 | .CodeMirror-measure { 293 | position: absolute; 294 | width: 100%; 295 | height: 0; 296 | overflow: hidden; 297 | visibility: hidden; 298 | } 299 | 300 | .CodeMirror-cursor { 301 | position: absolute; 302 | pointer-events: none; 303 | } 304 | .CodeMirror-measure pre { position: static; } 305 | 306 | div.CodeMirror-cursors { 307 | visibility: hidden; 308 | position: relative; 309 | z-index: 3; 310 | } 311 | div.CodeMirror-dragcursors { 312 | visibility: visible; 313 | } 314 | 315 | .CodeMirror-focused div.CodeMirror-cursors { 316 | visibility: visible; 317 | } 318 | 319 | .CodeMirror-selected { background: #d9d9d9; } 320 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 321 | .CodeMirror-crosshair { cursor: crosshair; } 322 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 323 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 324 | 325 | .cm-searching { 326 | background: #ffa; 327 | background: rgba(255, 255, 0, .4); 328 | } 329 | 330 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 331 | .CodeMirror span { *vertical-align: text-bottom; } 332 | 333 | /* Used to force a border model for a node */ 334 | .cm-force-border { padding-right: .1px; } 335 | 336 | @media print { 337 | /* Hide the cursor when printing */ 338 | .CodeMirror div.CodeMirror-cursors { 339 | visibility: hidden; 340 | } 341 | } 342 | 343 | /* See issue #2901 */ 344 | .cm-tab-wrap-hack:after { content: ''; } 345 | 346 | /* Help users use markselection to safely style text background */ 347 | span.CodeMirror-selectedtext { background: none; } 348 | -------------------------------------------------------------------------------- /playbook/assets/vendor/codemirror-js.js: -------------------------------------------------------------------------------- 1 | /* CodeMirror - Minified & Bundled 2 | Generated on 8/25/2016 with http://codemirror.net/doc/compress.html 3 | Version: HEAD 4 | 5 | Modes: 6 | - javascript.js 7 | */ 8 | 9 | !function(a){"object"==typeof exports&&"object"==typeof module?a(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],a):a(CodeMirror)}(function(a){"use strict";function b(a,b,c){return/^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(b.lastType)||"quasi"==b.lastType&&/\{\s*$/.test(a.string.slice(0,a.pos-(c||0)))}a.defineMode("javascript",function(c,d){function n(a){for(var c,b=!1,d=!1;null!=(c=a.next());){if(!b){if("/"==c&&!d)return;"["==c?d=!0:d&&"]"==c&&(d=!1)}b=!b&&"\\"==c}}function q(a,b,c){return o=a,p=c,b}function r(a,c){var d=a.next();if('"'==d||"'"==d)return c.tokenize=s(d),c.tokenize(a,c);if("."==d&&a.match(/^\d+(?:[eE][+\-]?\d+)?/))return q("number","number");if("."==d&&a.match(".."))return q("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(d))return q(d);if("="==d&&a.eat(">"))return q("=>","operator");if("0"==d&&a.eat(/x/i))return a.eatWhile(/[\da-f]/i),q("number","number");if("0"==d&&a.eat(/o/i))return a.eatWhile(/[0-7]/i),q("number","number");if("0"==d&&a.eat(/b/i))return a.eatWhile(/[01]/i),q("number","number");if(/\d/.test(d))return a.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/),q("number","number");if("/"==d)return a.eat("*")?(c.tokenize=t,t(a,c)):a.eat("/")?(a.skipToEnd(),q("comment","comment")):b(a,c,1)?(n(a),a.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/),q("regexp","string-2")):(a.eatWhile(l),q("operator","operator",a.current()));if("`"==d)return c.tokenize=u,u(a,c);if("#"==d)return a.skipToEnd(),q("error","error");if(l.test(d))return a.eatWhile(l),q("operator","operator",a.current());if(j.test(d)){a.eatWhile(j);var e=a.current(),f=k.propertyIsEnumerable(e)&&k[e];return f&&"."!=c.lastType?q(f.type,f.style,e):q("variable","variable",e)}}function s(a){return function(b,c){var e,d=!1;if(g&&"@"==b.peek()&&b.match(m))return c.tokenize=r,q("jsonld-keyword","meta");for(;null!=(e=b.next())&&(e!=a||d);)d=!d&&"\\"==e;return d||(c.tokenize=r),q("string","string")}}function t(a,b){for(var d,c=!1;d=a.next();){if("/"==d&&c){b.tokenize=r;break}c="*"==d}return q("comment","comment")}function u(a,b){for(var d,c=!1;null!=(d=a.next());){if(!c&&("`"==d||"$"==d&&a.eat("{"))){b.tokenize=r;break}c=!c&&"\\"==d}return q("quasi","string-2",a.current())}function w(a,b){b.fatArrowAt&&(b.fatArrowAt=null);var c=a.string.indexOf("=>",a.start);if(!(0>c)){for(var d=0,e=!1,f=c-1;f>=0;--f){var g=a.string.charAt(f),h=v.indexOf(g);if(h>=0&&3>h){if(!d){++f;break}if(0==--d){"("==g&&(e=!0);break}}else if(h>=3&&6>h)++d;else if(j.test(g))e=!0;else{if(/["'\/]/.test(g))return;if(e&&!d){++f;break}}}e&&!d&&(b.fatArrowAt=f)}}function y(a,b,c,d,e,f){this.indented=a,this.column=b,this.type=c,this.prev=e,this.info=f,null!=d&&(this.align=d)}function z(a,b){for(var c=a.localVars;c;c=c.next)if(c.name==b)return!0;for(var d=a.context;d;d=d.prev)for(var c=d.vars;c;c=c.next)if(c.name==b)return!0}function A(a,b,c,d,e){var f=a.cc;for(B.state=a,B.stream=e,B.marked=null,B.cc=f,B.style=b,a.lexical.hasOwnProperty("align")||(a.lexical.align=!0);;){var g=f.length?f.pop():h?M:L;if(g(c,d)){for(;f.length&&f[f.length-1].lex;)f.pop()();return B.marked?B.marked:"variable"==c&&z(a,d)?"variable-2":b}}}function C(){for(var a=arguments.length-1;a>=0;a--)B.cc.push(arguments[a])}function D(){return C.apply(null,arguments),!0}function E(a){function b(b){for(var c=b;c;c=c.next)if(c.name==a)return!0;return!1}var c=B.state;if(B.marked="def",c.context){if(b(c.localVars))return;c.localVars={name:a,next:c.localVars}}else{if(b(c.globalVars))return;d.globalVars&&(c.globalVars={name:a,next:c.globalVars})}}function G(){B.state.context={prev:B.state.context,vars:B.state.localVars},B.state.localVars=F}function H(){B.state.localVars=B.state.context.vars,B.state.context=B.state.context.prev}function I(a,b){var c=function(){var c=B.state,d=c.indented;if("stat"==c.lexical.type)d=c.lexical.indented;else for(var e=c.lexical;e&&")"==e.type&&e.align;e=e.prev)d=e.indented;c.lexical=new y(d,B.stream.column(),a,null,c.lexical,b)};return c.lex=!0,c}function J(){var a=B.state;a.lexical.prev&&(")"==a.lexical.type&&(a.indented=a.lexical.indented),a.lexical=a.lexical.prev)}function K(a){function b(c){return c==a?D():";"==a?C():D(b)}return b}function L(a,b){return"var"==a?D(I("vardef",b.length),na,K(";"),J):"keyword a"==a?D(I("form"),M,L,J):"keyword b"==a?D(I("form"),L,J):"{"==a?D(I("}"),fa,J):";"==a?D():"if"==a?("else"==B.state.lexical.info&&B.state.cc[B.state.cc.length-1]==J&&B.state.cc.pop()(),D(I("form"),M,L,J,sa)):"function"==a?D(ya):"for"==a?D(I("form"),ta,L,J):"variable"==a?D(I("stat"),$):"switch"==a?D(I("form"),M,I("}","switch"),K("{"),fa,J,J):"case"==a?D(M,K(":")):"default"==a?D(K(":")):"catch"==a?D(I("form"),G,K("("),za,K(")"),L,J,H):"class"==a?D(I("form"),Aa,J):"export"==a?D(I("stat"),Ea,J):"import"==a?D(I("stat"),Fa,J):"module"==a?D(I("form"),oa,I("}"),K("{"),fa,J,J):"async"==a?D(L):C(I("stat"),M,K(";"),J)}function M(a){return O(a,!1)}function N(a){return O(a,!0)}function O(a,b){if(B.state.fatArrowAt==B.stream.start){var c=b?W:V;if("("==a)return D(G,I(")"),da(oa,")"),J,K("=>"),c,H);if("variable"==a)return C(G,oa,K("=>"),c,H)}var d=b?S:R;return x.hasOwnProperty(a)?D(d):"function"==a?D(ya,d):"keyword c"==a||"async"==a?D(b?Q:P):"("==a?D(I(")"),P,K(")"),J,d):"operator"==a||"spread"==a?D(b?N:M):"["==a?D(I("]"),Ja,J,d):"{"==a?ea(aa,"}",null,d):"quasi"==a?C(T,d):"new"==a?D(X(b)):D()}function P(a){return a.match(/[;\}\)\],]/)?C():C(M)}function Q(a){return a.match(/[;\}\)\],]/)?C():C(N)}function R(a,b){return","==a?D(M):S(a,b,!1)}function S(a,b,c){var d=0==c?R:S,e=0==c?M:N;return"=>"==a?D(G,c?W:V,H):"operator"==a?/\+\+|--/.test(b)?D(d):"?"==b?D(M,K(":"),e):D(e):"quasi"==a?C(T,d):";"!=a?"("==a?ea(N,")","call",d):"."==a?D(_,d):"["==a?D(I("]"),P,K("]"),J,d):void 0:void 0}function T(a,b){return"quasi"!=a?C():"${"!=b.slice(b.length-2)?D(T):D(M,U)}function U(a){return"}"==a?(B.marked="string-2",B.state.tokenize=u,D(T)):void 0}function V(a){return w(B.stream,B.state),C("{"==a?L:M)}function W(a){return w(B.stream,B.state),C("{"==a?L:N)}function X(a){return function(b){return"."==b?D(a?Z:Y):C(a?N:M)}}function Y(a,b){return"target"==b?(B.marked="keyword",D(R)):void 0}function Z(a,b){return"target"==b?(B.marked="keyword",D(S)):void 0}function $(a){return":"==a?D(J,L):C(R,K(";"),J)}function _(a){return"variable"==a?(B.marked="property",D()):void 0}function aa(a,b){return"async"==a?(B.marked="property",D(aa)):"variable"==a||"keyword"==B.style?(B.marked="property",D("get"==b||"set"==b?ba:ca)):"number"==a||"string"==a?(B.marked=g?"property":B.style+" property",D(ca)):"jsonld-keyword"==a?D(ca):"modifier"==a?D(aa):"["==a?D(M,K("]"),ca):"spread"==a?D(M):":"==a?C(ca):void 0}function ba(a){return"variable"!=a?C(ca):(B.marked="property",D(ya))}function ca(a){return":"==a?D(N):"("==a?C(ya):void 0}function da(a,b){function c(d,e){if(","==d){var f=B.state.lexical;return"call"==f.info&&(f.pos=(f.pos||0)+1),D(function(c,d){return c==b||d==b?C():C(a)},c)}return d==b||e==b?D():D(K(b))}return function(d,e){return d==b||e==b?D():C(a,c)}}function ea(a,b,c){for(var d=3;d"==a?D(ia):void 0}function ka(a){return"variable"==a||"keyword"==B.style?(B.marked="property",D(ka)):":"==a?D(ia):void 0}function la(a){return"variable"==a?D(la):":"==a?D(ia):void 0}function ma(a,b){return"<"==b?D(da(ia,">"),ma):"["==a?D(K("]"),ma):void 0}function na(){return C(oa,ga,qa,ra)}function oa(a,b){return"modifier"==a?D(oa):"variable"==a?(E(b),D()):"spread"==a?D(oa):"["==a?ea(oa,"]"):"{"==a?ea(pa,"}"):void 0}function pa(a,b){return"variable"!=a||B.stream.match(/^\s*:/,!1)?("variable"==a&&(B.marked="property"),"spread"==a?D(oa):"}"==a?C():D(K(":"),oa,qa)):(E(b),D(qa))}function qa(a,b){return"="==b?D(N):void 0}function ra(a){return","==a?D(na):void 0}function sa(a,b){return"keyword b"==a&&"else"==b?D(I("form","else"),L,J):void 0}function ta(a){return"("==a?D(I(")"),ua,K(")"),J):void 0}function ua(a){return"var"==a?D(na,K(";"),wa):";"==a?D(wa):"variable"==a?D(va):C(M,K(";"),wa)}function va(a,b){return"in"==b||"of"==b?(B.marked="keyword",D(M)):D(R,wa)}function wa(a,b){return";"==a?D(xa):"in"==b||"of"==b?(B.marked="keyword",D(M)):C(M,K(";"),xa)}function xa(a){")"!=a&&D(M)}function ya(a,b){return"*"==b?(B.marked="keyword",D(ya)):"variable"==a?(E(b),D(ya)):"("==a?D(G,I(")"),da(za,")"),J,ga,L,H):void 0}function za(a){return"spread"==a?D(za):C(oa,ga,ha)}function Aa(a,b){return"variable"==a?(E(b),D(Ba)):void 0}function Ba(a,b){return"extends"==b?D(i?ia:M,Ba):"{"==a?D(I("}"),Ca,J):void 0}function Ca(a,b){return"variable"==a||"keyword"==B.style?"static"==b?(B.marked="keyword",D(Ca)):(B.marked="property","get"==b||"set"==b?D(Da,ya,Ca):D(ya,Ca)):"*"==b?(B.marked="keyword",D(Ca)):";"==a?D(Ca):"}"==a?D():void 0}function Da(a){return"variable"!=a?C():(B.marked="property",D())}function Ea(a,b){return"*"==b?(B.marked="keyword",D(Ia,K(";"))):"default"==b?(B.marked="keyword",D(M,K(";"))):C(L)}function Fa(a){return"string"==a?D():C(Ga,Ia)}function Ga(a,b){return"{"==a?ea(Ga,"}"):("variable"==a&&E(b),"*"==b&&(B.marked="keyword"),D(Ha))}function Ha(a,b){return"as"==b?(B.marked="keyword",D(Ga)):void 0}function Ia(a,b){return"from"==b?(B.marked="keyword",D(M)):void 0}function Ja(a){return"]"==a?D():C(da(N,"]"))}function Ka(a,b){return"operator"==a.lastType||","==a.lastType||l.test(b.charAt(0))||/[,.]/.test(b.charAt(0))}var o,p,e=c.indentUnit,f=d.statementIndent,g=d.jsonld,h=d.json||g,i=d.typescript,j=d.wordCharacters||/[\w$\xa1-\uffff]/,k=function(){function a(a){return{type:a,style:"keyword"}}var b=a("keyword a"),c=a("keyword b"),d=a("keyword c"),e=a("operator"),f={type:"atom",style:"atom"},g={"if":a("if"),"while":b,"with":b,"else":c,"do":c,"try":c,"finally":c,"return":d,"break":d,"continue":d,"new":a("new"),"delete":d,"throw":d,"debugger":d,"var":a("var"),"const":a("var"),let:a("var"),"function":a("function"),"catch":a("catch"),"for":a("for"),"switch":a("switch"),"case":a("case"),"default":a("default"),"in":e,"typeof":e,"instanceof":e,"true":f,"false":f,"null":f,undefined:f,NaN:f,Infinity:f,"this":a("this"),"class":a("class"),"super":a("atom"),"yield":d,"export":a("export"),"import":a("import"),"extends":d,await:d,async:a("async")};if(i){var h={type:"variable",style:"variable-3"},j={"interface":a("class"),"implements":d,namespace:d,module:a("module"),"enum":a("module"),"public":a("modifier"),"private":a("modifier"),"protected":a("modifier"),"abstract":a("modifier"),as:e,string:h,number:h,"boolean":h,any:h};for(var k in j)g[k]=j[k]}return g}(),l=/[+\-*&%=<>!?|~^]/,m=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/,v="([{}])",x={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,"this":!0,"jsonld-keyword":!0},B={state:null,column:null,marked:null,cc:null},F={name:"this",next:{name:"arguments"}};return J.lex=!0,{startState:function(a){var b={tokenize:r,lastType:"sof",cc:[],lexical:new y((a||0)-e,0,"block",!1),localVars:d.localVars,context:d.localVars&&{vars:d.localVars},indented:a||0};return d.globalVars&&"object"==typeof d.globalVars&&(b.globalVars=d.globalVars),b},token:function(a,b){if(a.sol()&&(b.lexical.hasOwnProperty("align")||(b.lexical.align=!1),b.indented=a.indentation(),w(a,b)),b.tokenize!=t&&a.eatSpace())return null;var c=b.tokenize(a,b);return"comment"==o?c:(b.lastType="operator"!=o||"++"!=p&&"--"!=p?o:"incdec",A(b,c,o,p,a))},indent:function(b,c){if(b.tokenize==t)return a.Pass;if(b.tokenize!=r)return 0;var g=c&&c.charAt(0),h=b.lexical;if(!/^\s*else\b/.test(c))for(var i=b.cc.length-1;i>=0;--i){var j=b.cc[i];if(j==J)h=h.prev;else if(j!=sa)break}"stat"==h.type&&"}"==g&&(h=h.prev),f&&")"==h.type&&"stat"==h.prev.type&&(h=h.prev);var k=h.type,l=g==k;return"vardef"==k?h.indented+("operator"==b.lastType||","==b.lastType?h.info+1:0):"form"==k&&"{"==g?h.indented:"form"==k?h.indented+e:"stat"==k?h.indented+(Ka(b,c)?f||e:0):"switch"!=h.info||l||0==d.doubleIndentSwitch?h.align?h.column+(l?0:1):h.indented+(l?0:e):h.indented+(/^(?:case|default)\b/.test(c)?e:2*e)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:h?null:"/*",blockCommentEnd:h?null:"*/",lineComment:h?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:h?"json":"javascript",jsonldMode:g,jsonMode:h,expressionAllowed:b,skipExpression:function(a){var b=a.cc[a.cc.length-1];(b==M||b==N)&&a.cc.pop()}}}),a.registerHelper("wordChars","javascript",/[\w$]/),a.defineMIME("text/javascript","javascript"),a.defineMIME("text/ecmascript","javascript"),a.defineMIME("application/javascript","javascript"),a.defineMIME("application/x-javascript","javascript"),a.defineMIME("application/ecmascript","javascript"),a.defineMIME("application/json",{name:"javascript",json:!0}),a.defineMIME("application/x-json",{name:"javascript",json:!0}),a.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),a.defineMIME("text/typescript",{name:"javascript",typescript:!0}),a.defineMIME("application/typescript",{name:"javascript",typescript:!0})}); --------------------------------------------------------------------------------