├── .gitignore
├── .stylelintrc.json
├── .textlintrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── custom
│ ├── SnoreToast.exe
│ └── terminal-notifier.app
│ │ └── Contents
│ │ ├── Info.plist
│ │ ├── MacOS
│ │ └── terminal-notifier
│ │ ├── PkgInfo
│ │ └── Resources
│ │ ├── Terminal.icns
│ │ └── en.lproj
│ │ ├── Credits.rtf
│ │ ├── InfoPlist.strings
│ │ └── MainMenu.nib
├── fonts
│ ├── Montserrat-Regular.ttf
│ └── fontawesome-webfont.woff2
├── images
│ ├── redmine_icon_black_24.png
│ ├── redmine_icon_black_24_notification.png
│ ├── redmine_icon_color_24.png
│ ├── redmine_icon_color_24_notification.png
│ └── redmine_icon_color_64.png
├── index.html
├── index.js
├── main.js
├── package.json
├── stylesheets
│ ├── font-awesome.min.css
│ └── index.css
└── yarn.lock
├── bin
├── clean.sh
├── lint-css.sh
├── lint-js.sh
├── lint-text.sh
├── pack-all.sh
├── pack-mac.sh
├── pack-win.sh
├── test.sh
└── travis-build.sh
├── build
├── background.png
├── icon.icns
├── icon.ico
└── install-spinner.gif
├── examples
├── icon_osx_normal.png
├── icon_osx_notification.png
├── icon_win_normal.png
├── icon_win_notification.png
├── notification_osx_10.10.png
├── notification_win_10.png
└── redmine_notifier_settings.png
├── inch.json
├── package.json
├── test
└── test.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | node_modules
4 | dist
5 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard",
3 | "plugins": [
4 | "stylelint-order"
5 | ],
6 | "rules": {
7 | "font-family-no-missing-generic-family-keyword": null,
8 | "order/properties-alphabetical-order": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.textlintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "write-good": {
4 | "passive": false,
5 | "adverb": false
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os:
2 | - osx
3 | language: node_js
4 | node_js:
5 | - "6"
6 | branches:
7 | only:
8 | - master
9 | script: ./bin/travis-build.sh
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## 0.8.0 (2017-02-26)
4 |
5 | ### Production
6 |
7 | * Fix bug that closed issues are not fetched
8 | * Fix bug that more than 25 issues are not fetched at once
9 | * Remove small terminal icon in main notification for macOS
10 | * Change icon in main notification for Windows
11 | * Add `About Redmine Notifier` menu
12 | * Change style of text fields
13 | * Change app ID
14 | * Update to Electron 1.4.15
15 | * Update notie dependency to 3.9.5
16 | * Update node-notifier dependency to 5.0.2
17 |
18 | ### Development
19 |
20 | * Add `postinstall` task
21 | * Add `clean` task
22 | * Remove redundant `build` task
23 | * Use yarn instead of npm
24 | * Use ava instead of mocha, chai, and chai-as-promised
25 | * Use xo instead of eslint
26 | * Use stylelint instead of csslint
27 | * Update electron-builder dependency to 14.4.0
28 | * Update spectron dependency to ~3.4.0
29 | * Update textlint dependency to ^7.2.2
30 | * Update to Node.js 6 on Travis CI
31 |
32 | ## 0.7.1 (2016-09-11)
33 |
34 | ### Production
35 |
36 | * Fix errors that occur when a new entry form is added
37 |
38 | ## 0.7.0 (2016-09-10)
39 |
40 | ### Production
41 |
42 | * Enable to store a lot of settings
43 | * Change timing of clearing sub notification
44 | * Update animated GIF of Windows installer
45 | * Fix whitespace in the settings window title
46 | * Fix redundant save message
47 | * Change CSS properties in alphabetical order
48 | * Update to Electron 1.3.5
49 | * Update notie dependency to 3.9.4
50 | * Update node-notifier dependency to ^4.6.1
51 |
52 | ### Development
53 |
54 | * Add version to suffix of installer filename
55 | * Update electron-prebuilt dependency to 1.3.5
56 | * Update electron-builder dependency to ~6.6.1
57 | * Update spectron dependency to ~3.3.0
58 | * Update mocha dependency to ^3.0.2
59 | * Update eslint dependency to ^3.5.0
60 | * Update csslint dependency to ^1.0.2
61 | * Update textlint dependency to ^7.1.1
62 |
63 | ## 0.6.0 (2016-07-10)
64 |
65 | ### Production
66 |
67 | * Update to Electron 1.2.6
68 | * Change animated GIF to display during install (Windows only)
69 | * Change syntax to ES2015
70 | * Replace `for` statement with `filter()`
71 | * Remove verbose `label` and `accelerator` options in `MenuItem`
72 |
73 | ### Development
74 |
75 | * Update electron-prebuilt dependency to 1.2.6
76 | * Update electron-builder dependency to ~5.12.0
77 | * Add simple tests using spectron, mocha, chai, and chai-as-promised
78 | * Add textlint for linting Markdown file
79 | * Update eslint dependency to ^3.0.1
80 | * Add eslint rules for jsdoc
81 | * Change `#!/bin/bash` -> `#!/usr/bin/env bash` for flexibility
82 |
83 | ## 0.5.0 (2016-05-03)
84 |
85 | ### Production
86 |
87 | * Add notification icons
88 | * Add "Open Most Recent Issue in Browser" to context menu
89 | * Decrease useless access by keeping fetch mode
90 | * Update notie dependency to ^3.2.0
91 | * Add `return this` for method chaining
92 | * Change `window.onload` -> `window.addEventListener('load')` for consistency
93 | * Change `'use strict'` to global scope
94 | * Remove unused variables
95 | * Update to Electron 0.37.8
96 |
97 | ### Development
98 |
99 | * Update electron-builder dependency to ^3.16.0
100 | * Remove electron-packager from package.json
101 | * Add new cross-platform package options
102 | * Remove unused configuration file for building installers
103 | * Add eslint for linting JS file
104 | * Add csslint for linting CSS file
105 | * Add `npm run release`
106 | * Update electron-prebuilt dependency to 0.37.8
107 |
108 | ## 0.4.0 (2016-01-17)
109 |
110 | ### Production
111 |
112 | * Add fallback when Redmine API doesn't accept time format
113 | * Update to Electron 0.36.4
114 |
115 | ### Development
116 |
117 | * Avoid outputting a console message like "Couldn't set selectedTextBackgroundColor from default ()" when selecting text in the settings window
118 | * Update electron-prebuilt dependency to ^0.36.4
119 |
120 | ## 0.3.0 (2016-01-11)
121 |
122 | ### Production
123 |
124 | * Add `Project ID` setting
125 | * Tweak outline of buttons
126 | * Change coding style of IIFE
127 | * Update to Electron 0.36.3
128 |
129 | ### Development
130 |
131 | * Extract build scripts
132 | * Add `npm run prepare`
133 | * Do not remove app/node_modules directory when building apps
134 | * Remove verbose `clean` script
135 | * Fix `--ignore` option
136 | * Update electron-builder dependency to ^2.6.0
137 | * Update electron-prebuilt dependency to ^0.36.3
138 |
139 | ## 0.2.0 (2015-12-27)
140 |
141 | ### Production
142 |
143 | * Package app into asar archive
144 | * Rename installer file
145 | * Remove crash-reporter
146 | * Update to Electron 0.36.2
147 |
148 | ### Development
149 |
150 | * Ignore .DS_Store and npm-debug.log when building apps
151 | * Run `rm -rf ./app/node_modules && npm install --prefix ./app` before packaging
152 | * Update electron-builder dependency to ^2.5.0
153 | * Update electron-packager dependency to ^5.2.0
154 | * Update electron-prebuilt dependency to ^0.36.2
155 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2019 emsk
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redmine Notifier
2 |
3 | [](https://travis-ci.org/emsk/redmine-notifier)
4 | [](https://codeclimate.com/github/emsk/redmine-notifier)
5 | [](http://inch-ci.org/github/emsk/redmine-notifier)
6 | [](https://github.com/sindresorhus/xo)
7 | [](LICENSE)
8 |
9 | Redmine Notifier is a simple updated issues checker that runs in the background.
10 | It sends a desktop notification if there are any updates in issues.
11 |
12 | ## Installation
13 |
14 | Installers for macOS and Windows can be found on the [releases](../../releases) page.
15 |
16 | For Windows, the app is installed to `C:\Users\[UserName]\AppData\Local\redmine\app-[AppVersion]\Redmine Notifier.exe`.
17 |
18 | ## Main Notifications
19 |
20 | The number of issues that were updated after previous fetch is shown in parentheses.
21 | Also, the subject is shown only about the latest issue.
22 |
23 | 
24 | 
25 |
26 | ## Sub Notifications
27 |
28 | When Redmine Notifier is started, an icon appears in the menu bar or task tray.
29 | If there is a notification in the most recent fetch, the icon is replaced by the notification icon.
30 |
31 | ##### Menu Icon (macOS)
32 |
33 | Normal:  Notification: 
34 |
35 | ##### Tray Icon (Windows)
36 |
37 | Normal:  Notification: 
38 |
39 | ## Settings
40 |
41 | Open the context menu and select "Preferences".
42 |
43 | 
44 |
45 | ## Development
46 |
47 | Redmine Notifier is powered by [Electron](http://electron.atom.io/), so we can develop it with web technologies.
48 |
49 | ### Dependencies
50 |
51 | ##### Production
52 |
53 | * [node-notifier](https://github.com/mikaelbr/node-notifier)
54 | * [notie](https://github.com/jaredreich/notie)
55 |
56 | ##### Development
57 |
58 | * [ava](https://github.com/avajs/ava)
59 | * [electron](https://github.com/electron/electron)
60 | * [electron-builder](https://github.com/electron-userland/electron-builder)
61 | * [spectron](https://github.com/electron/spectron)
62 | * [stylelint](https://github.com/stylelint/stylelint)
63 | * [stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard)
64 | * [textlint](https://github.com/textlint/textlint)
65 | * [textlint-rule-write-good](https://github.com/nodaguti/textlint-rule-write-good)
66 | * [xo](https://github.com/sindresorhus/xo)
67 |
68 | See `dependencies` and `devDependencies` in [`package.json`](package.json).
69 |
70 | ### Installing dependencies
71 |
72 | ```sh
73 | cd /path/to/redmine-notifier
74 | yarn install
75 | ```
76 |
77 | ### Starting app
78 |
79 | ```sh
80 | yarn start
81 | ```
82 |
83 | ### Building installers
84 |
85 | ```sh
86 | yarn run pack
87 | ```
88 |
89 | ### Linting JavaScript, CSS, and Markdown files
90 |
91 | ```sh
92 | yarn run lint
93 | ```
94 |
95 | ### Testing app
96 |
97 | ```sh
98 | yarn test
99 | ```
100 |
101 | See `scripts` in [`package.json`](package.json) with regard to other commands.
102 |
103 | ## Contributing
104 |
105 | 1. Fork it ( https://github.com/emsk/redmine-notifier/fork )
106 | 2. Create your feature branch (`git checkout -b my-new-feature`)
107 | 3. Commit your changes (`git commit -am 'Add some feature'`)
108 | 4. Push to the branch (`git push origin my-new-feature`)
109 | 5. Create a new Pull Request
110 |
111 | ## Related
112 |
113 | * [Redmine Now](https://github.com/emsk/redmine-now) - A desktop app to know what's happening now on your Redmine
114 |
115 | ## License
116 |
117 | [MIT](LICENSE)
118 |
--------------------------------------------------------------------------------
/app/custom/SnoreToast.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/custom/SnoreToast.exe
--------------------------------------------------------------------------------
/app/custom/terminal-notifier.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildMachineOSBuild
6 | 15G1004
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleExecutable
10 | terminal-notifier
11 | CFBundleIconFile
12 | Terminal
13 | CFBundleIdentifier
14 | com.emsk.terminal-notifier
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | terminal-notifier
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | 1.7.1
23 | CFBundleSignature
24 | ????
25 | CFBundleSupportedPlatforms
26 |
27 | MacOSX
28 |
29 | CFBundleVersion
30 | 16
31 | DTCompiler
32 | com.apple.compilers.llvm.clang.1_0
33 | DTPlatformBuild
34 | 8A218a
35 | DTPlatformVersion
36 | GM
37 | DTSDKBuild
38 | 16A300
39 | DTSDKName
40 | macosx10.12
41 | DTXcode
42 | 0800
43 | DTXcodeBuild
44 | 8A218a
45 | LSMinimumSystemVersion
46 | 10.8
47 | LSUIElement
48 |
49 | NSHumanReadableCopyright
50 | Copyright © 2012-2016 Eloy Durán, Julien Blanchard. All rights reserved.
51 | NSMainNibFile
52 | MainMenu
53 | NSPrincipalClass
54 | NSApplication
55 | NSUserNotificationAlertStyle
56 | alert
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/custom/terminal-notifier.app/Contents/MacOS/terminal-notifier:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/custom/terminal-notifier.app/Contents/MacOS/terminal-notifier
--------------------------------------------------------------------------------
/app/custom/terminal-notifier.app/Contents/PkgInfo:
--------------------------------------------------------------------------------
1 | APPL????
--------------------------------------------------------------------------------
/app/custom/terminal-notifier.app/Contents/Resources/Terminal.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/custom/terminal-notifier.app/Contents/Resources/Terminal.icns
--------------------------------------------------------------------------------
/app/custom/terminal-notifier.app/Contents/Resources/en.lproj/Credits.rtf:
--------------------------------------------------------------------------------
1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;}
2 | {\colortbl;\red255\green255\blue255;}
3 | \paperw9840\paperh8400
4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
5 |
6 | \f0\b\fs24 \cf0 Engineering:
7 | \b0 \
8 | Some people\
9 | \
10 |
11 | \b Human Interface Design:
12 | \b0 \
13 | Some other people\
14 | \
15 |
16 | \b Testing:
17 | \b0 \
18 | Hopefully not nobody\
19 | \
20 |
21 | \b Documentation:
22 | \b0 \
23 | Whoever\
24 | \
25 |
26 | \b With special thanks to:
27 | \b0 \
28 | Mom\
29 | }
30 |
--------------------------------------------------------------------------------
/app/custom/terminal-notifier.app/Contents/Resources/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/custom/terminal-notifier.app/Contents/Resources/en.lproj/InfoPlist.strings
--------------------------------------------------------------------------------
/app/custom/terminal-notifier.app/Contents/Resources/en.lproj/MainMenu.nib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/custom/terminal-notifier.app/Contents/Resources/en.lproj/MainMenu.nib
--------------------------------------------------------------------------------
/app/fonts/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/fonts/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/app/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/app/images/redmine_icon_black_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/images/redmine_icon_black_24.png
--------------------------------------------------------------------------------
/app/images/redmine_icon_black_24_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/images/redmine_icon_black_24_notification.png
--------------------------------------------------------------------------------
/app/images/redmine_icon_color_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/images/redmine_icon_color_24.png
--------------------------------------------------------------------------------
/app/images/redmine_icon_color_24_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/images/redmine_icon_color_24_notification.png
--------------------------------------------------------------------------------
/app/images/redmine_icon_color_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/app/images/redmine_icon_color_64.png
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Preferences
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Redmine Notifier
13 |
14 |
42 |
43 |
55 |
56 |
57 |
61 |
65 |
66 |
67 |
68 |
69 |
72 |
75 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (() => {
4 | const isMac = process.platform === 'darwin';
5 | const defaultFetchIntervalSec = 600;
6 | const notieDisplaySec = 1.5;
7 | const colorIconFilename64 = 'redmine_icon_color_64.png';
8 | const blackIconFilename24 = 'redmine_icon_black_24.png';
9 | const blackIconFilename24Notification = 'redmine_icon_black_24_notification.png';
10 | const colorIconFilename24 = 'redmine_icon_color_24.png';
11 | const colorIconFilename24Notification = 'redmine_icon_color_24_notification.png';
12 | const fetchMode = Object.freeze({time: 'TIME', date: 'DATE'});
13 |
14 | const {remote} = require('electron');
15 | const {app, dialog, shell, Menu, Tray} = remote;
16 | const fs = require('fs');
17 | const notie = require('notie');
18 |
19 | const appName = app.getName();
20 | const appCopyright = 'Copyright (c) 2015-2019 emsk';
21 |
22 | let appDir = `${__dirname}.unpacked`; // Production
23 | try {
24 | fs.statSync(appDir);
25 | } catch (err) {
26 | appDir = __dirname; // Development
27 | }
28 |
29 | let nodeNotifier = require('node-notifier');
30 | if (isMac) {
31 | nodeNotifier = new nodeNotifier.NotificationCenter({
32 | customPath: `${appDir}/custom/terminal-notifier.app/Contents/MacOS/terminal-notifier`
33 | });
34 | } else {
35 | nodeNotifier = new nodeNotifier.WindowsToaster({
36 | customPath: `${appDir}/custom/SnoreToast.exe`
37 | });
38 | }
39 |
40 | const appIconFilePath = isMac ? null : `${appDir}/images/${colorIconFilename64}`;
41 |
42 | let notifierScreen = null;
43 |
44 | /**
45 | * Class to check updated issues.
46 | */
47 | class RedmineNotifier {
48 | /**
49 | * Initialize the RedmineNotifier object.
50 | * @constructor
51 | * @param {number} index - Index of the object.
52 | */
53 | constructor(index) {
54 | this._newFlag = false;
55 | this._index = index;
56 | this._lastExecutionTime = null;
57 | this._settings = null;
58 | this._fetchTimer = null;
59 | this._fetchMode = null;
60 | this._mostRecentIssueId = null;
61 | }
62 |
63 | /**
64 | * Set flag of whether the object is new.
65 | * @param {boolean} newFlag - true if the object is new.
66 | * @return {Object} Current object.
67 | */
68 | setNewFlag(newFlag) {
69 | this._newFlag = newFlag;
70 | return this;
71 | }
72 |
73 | /**
74 | * Display the settings on the screen.
75 | * @return {Object} Current object.
76 | */
77 | displaySettings() {
78 | document.getElementById('url').value = this._settings.url;
79 | document.getElementById('api-key').value = this._settings.apiKey;
80 | document.getElementById('project-id').value = this._settings.projectId;
81 | document.getElementById('fetch-interval-sec').value = this._settings.fetchIntervalSec;
82 | return this;
83 | }
84 |
85 | /**
86 | * Get the settings from the screen.
87 | * @return {Object} Settings.
88 | */
89 | getPageSettings() {
90 | return {
91 | url: document.getElementById('url').value,
92 | apiKey: document.getElementById('api-key').value,
93 | projectId: document.getElementById('project-id').value,
94 | fetchIntervalSec: document.getElementById('fetch-interval-sec').value
95 | };
96 | }
97 |
98 | /**
99 | * Read the settings from the screen.
100 | * @return {Object} Current object.
101 | */
102 | readScreenSettings() {
103 | this._settings = this.getPageSettings();
104 | return this;
105 | }
106 |
107 | /**
108 | * Read the settings from the localStorage.
109 | * @return {Object} Current object.
110 | */
111 | readStoredSettings() {
112 | this._settings = {
113 | url: localStorage.getItem(`url${this._index}`),
114 | apiKey: localStorage.getItem(`apiKey${this._index}`),
115 | projectId: localStorage.getItem(`projectId${this._index}`),
116 | fetchIntervalSec: localStorage.getItem(`fetchIntervalSec${this._index}`)
117 | };
118 |
119 | return this;
120 | }
121 |
122 | /**
123 | * Get the setting from the localStorage.
124 | * @param {string} key - Setting key.
125 | * @return {string} Setting value.
126 | */
127 | getStoredSetting(key) {
128 | return localStorage.getItem(`${key}${this._index}`);
129 | }
130 |
131 | /**
132 | * Update the stored last execution time.
133 | * @return {Object} Current object.
134 | */
135 | updateLastExecutionTime() {
136 | this._lastExecutionTime = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
137 | localStorage.setItem(`lastExecutionTime${this._index}`, this._lastExecutionTime);
138 | return this;
139 | }
140 |
141 | /**
142 | * Update the stored settings.
143 | * @return {Object} Current object.
144 | */
145 | updateSettings() {
146 | localStorage.setItem(`url${this._index}`, this._settings.url);
147 | localStorage.setItem(`apiKey${this._index}`, this._settings.apiKey);
148 | localStorage.setItem(`projectId${this._index}`, this._settings.projectId);
149 | localStorage.setItem(`fetchIntervalSec${this._index}`, this._settings.fetchIntervalSec);
150 | return this;
151 | }
152 |
153 | /**
154 | * Delete the settings.
155 | * @return {Object} Current object.
156 | */
157 | deleteStoredSettings() {
158 | localStorage.removeItem(`url${this._index}`);
159 | localStorage.removeItem(`apiKey${this._index}`);
160 | localStorage.removeItem(`projectId${this._index}`);
161 | localStorage.removeItem(`fetchIntervalSec${this._index}`);
162 | return this;
163 | }
164 |
165 | /**
166 | * Validate the settings.
167 | * @return {boolean} true if valid.
168 | */
169 | validateSettings() {
170 | if (this._settings.url && this._settings.apiKey) {
171 | return true;
172 | }
173 | notie.alert({
174 | type: 'error',
175 | text: 'Please enter required fields.',
176 | time: notieDisplaySec
177 | });
178 | return false;
179 | }
180 |
181 | /**
182 | * Initialize the fetch function.
183 | * @return {Object} Current object.
184 | */
185 | initFetch() {
186 | const intervalMsec = 1000 * (this._settings.fetchIntervalSec || defaultFetchIntervalSec);
187 |
188 | clearInterval(this._fetchTimer);
189 |
190 | this._fetchTimer = setInterval(() => {
191 | this.fetch(this._fetchMode || fetchMode.time);
192 | }, intervalMsec);
193 |
194 | return this;
195 | }
196 |
197 | /**
198 | * Fetch updated issues by using Redmine REST API.
199 | * @param {string} mode - Time or date.
200 | * @return {Object} Current object.
201 | */
202 | fetch(mode) {
203 | const xhr = new XMLHttpRequest();
204 |
205 | xhr.onreadystatechange = () => {
206 | if (xhr.readyState === 4) {
207 | this.handleResponseFetch(mode, xhr.status, xhr.responseText);
208 | }
209 | };
210 |
211 | xhr.open('GET', `${this._settings.url}/issues.json${this.getRequestParams(mode, this._settings.projectId)}`);
212 | xhr.setRequestHeader('X-Redmine-API-Key', this._settings.apiKey);
213 | xhr.send();
214 |
215 | return this;
216 | }
217 |
218 | /**
219 | * Handle the response for the fetch.
220 | * @param {string} mode - Time or date.
221 | * @param {number} status - Response status.
222 | * @param {string} responseText - Response text.
223 | * @return {Object} Current object.
224 | */
225 | handleResponseFetch(mode, status, responseText) {
226 | if (mode === fetchMode.time) {
227 | if (status === 200) {
228 | const response = JSON.parse(responseText);
229 | this.notify(response.issues, this.isOverPage(response))
230 | .updateLastExecutionTime();
231 | } else if (status === 422) {
232 | // Retry with date mode if Redmine API doesn't accept time format
233 | this._fetchMode = fetchMode.date;
234 | this.fetch(fetchMode.date);
235 | }
236 | } else {
237 | if (status === 200) {
238 | const response = JSON.parse(responseText);
239 | this.notify(this.pickIssues(response.issues), this.isOverPage(response));
240 | }
241 |
242 | this.updateLastExecutionTime();
243 | }
244 |
245 | return this;
246 | }
247 |
248 | /**
249 | * Check whether issues over 1 page.
250 | * @param {Object} response - Response.
251 | * @return {boolean} true if over 1 page.
252 | */
253 | isOverPage(response) {
254 | return response.total_count > response.limit;
255 | }
256 |
257 | /**
258 | * Get issues which were updated after last execution time.
259 | * @param {string} responseIssues - Response issues.
260 | * @return {Object[]} Processed issues.
261 | */
262 | pickIssues(responseIssues) {
263 | const lastExecutionTime = new Date(this._lastExecutionTime).getTime();
264 |
265 | const issues = responseIssues.filter(issue => {
266 | const updatedTime = new Date(issue.updated_on).getTime();
267 | return updatedTime >= lastExecutionTime;
268 | });
269 |
270 | return issues;
271 | }
272 |
273 | /**
274 | * Test the connection to the Redmine.
275 | * @param {string} mode - Time or date.
276 | * @return {Object} Current object.
277 | */
278 | testConnection(mode) {
279 | const xhr = new XMLHttpRequest();
280 | const pageSettings = this.getPageSettings();
281 |
282 | xhr.onreadystatechange = () => {
283 | if (xhr.readyState === 4) {
284 | this.handleResponseTestConnection(mode, xhr.status);
285 | }
286 | };
287 |
288 | xhr.open('GET', `${pageSettings.url}/issues.json${this.getRequestParams(mode, pageSettings.projectId)}`);
289 | xhr.setRequestHeader('X-Redmine-API-Key', pageSettings.apiKey);
290 | xhr.send();
291 |
292 | return this;
293 | }
294 |
295 | /**
296 | * Handle the response for the test connection.
297 | * @param {string} mode - Time or date.
298 | * @param {number} status - Response status.
299 | * @return {Object} Current object.
300 | */
301 | handleResponseTestConnection(mode, status) {
302 | if (status === 200) {
303 | notie.alert({
304 | type: 'success',
305 | text: 'Connection succeeded.',
306 | time: notieDisplaySec
307 | });
308 | return this;
309 | }
310 |
311 | // Retry with date mode if Redmine API doesn't accept time format
312 | if (mode === fetchMode.time && status === 422) {
313 | this.testConnection(fetchMode.date);
314 | return this;
315 | }
316 |
317 | notie.alert({
318 | type: 'error',
319 | text: 'Connection failed.',
320 | time: notieDisplaySec
321 | });
322 |
323 | return this;
324 | }
325 |
326 | /**
327 | * Get the request parameters.
328 | * @param {string} mode - Time or date.
329 | * @param {string} projectId - Project ID (a numeric value, not a project identifier).
330 | * @return {string} Request parameters.
331 | */
332 | getRequestParams(mode, projectId) {
333 | const params = [
334 | `updated_on=%3E%3D${this.getLastExecutionTime(mode)}`,
335 | 'status_id=*',
336 | 'sort=updated_on:desc',
337 | 'limit=100'
338 | ];
339 |
340 | if (typeof projectId === 'string' && projectId !== '') {
341 | params.unshift(`project_id=${projectId}`);
342 | }
343 |
344 | return `?${params.join('&')}`;
345 | }
346 |
347 | /**
348 | * Get last execution time by mode.
349 | * @param {string} mode - Time or date.
350 | * @return {string} Last execution time.
351 | */
352 | getLastExecutionTime(mode) {
353 | if (mode === fetchMode.time) {
354 | return this._lastExecutionTime;
355 | }
356 | return this._lastExecutionTime.replace(/T.*/, ''); // Date
357 | }
358 |
359 | /**
360 | * Send the desktop notification.
361 | * @param {Object} issues - All of updated issues.
362 | * @param {boolean} isOverPage - true if over 1 page.
363 | * @return {Object} Current object.
364 | */
365 | notify(issues, isOverPage) {
366 | const issueCount = issues.length;
367 |
368 | if (issueCount === 0) {
369 | return this;
370 | }
371 |
372 | this._mostRecentIssueId = issues[0].id;
373 | notifierScreen.setNotificationIcon(this._index);
374 |
375 | // Display the latest issue's subject only
376 | nodeNotifier.notify({
377 | title: this.buildNotificationTitle(issueCount, isOverPage),
378 | message: issues[0].subject,
379 | wait: true
380 | });
381 |
382 | nodeNotifier.removeAllListeners();
383 |
384 | nodeNotifier.once('click', () => {
385 | shell.openExternal(`${this._settings.url}/issues/${this._mostRecentIssueId}`);
386 | notifierScreen.setNormalIcon();
387 | nodeNotifier.removeAllListeners();
388 | });
389 |
390 | nodeNotifier.once('timeout', () => {
391 | nodeNotifier.removeAllListeners();
392 | });
393 |
394 | return this;
395 | }
396 |
397 | /**
398 | * Build a notification title.
399 | * @param {number} issueCount - Count of issues.
400 | * @param {boolean} isOverPage - true if over 1 page.
401 | * @return {string} Notification title.
402 | */
403 | buildNotificationTitle(issueCount, isOverPage) {
404 | let title = `(${issueCount}`;
405 | if (isOverPage) {
406 | title += '+';
407 | }
408 | title += ') Redmine Notifier';
409 |
410 | return title;
411 | }
412 | }
413 |
414 | /**
415 | * Class to handle settings screen.
416 | */
417 | class RedmineNotifierScreen {
418 | /**
419 | * Initialize the RedmineNotifierScreen object.
420 | * @constructor
421 | */
422 | constructor() {
423 | this._notifiers = null;
424 | this._currentNotifierIndex = null;
425 | this._tray = null;
426 | this._contextMenu = null;
427 | this._mostRecentNotifierIndex = null;
428 |
429 | if (isMac) {
430 | this._iconFilePath = `${__dirname}/images/${blackIconFilename24}`;
431 | this._notificationIconFilePath = `${__dirname}/images/${blackIconFilename24Notification}`;
432 | } else {
433 | this._iconFilePath = `${__dirname}/images/${colorIconFilename24}`;
434 | this._notificationIconFilePath = `${__dirname}/images/${colorIconFilename24Notification}`;
435 | }
436 | }
437 |
438 | /**
439 | * Initialize the RedmineNotifier objects.
440 | * @param {RedmineNotifier[]} notifiers - RedmineNotifier objects.
441 | * @return {Object} Current object.
442 | */
443 | initNotifiers(notifiers) {
444 | this._notifiers = notifiers;
445 | this._currentNotifierIndex = Number(localStorage.getItem('lastDisplayedNotifierIndex'));
446 |
447 | const notifier = this._notifiers[this._currentNotifierIndex];
448 | notifier.displaySettings();
449 |
450 | return this;
451 | }
452 |
453 | /**
454 | * Initialize the application menu and context menu.
455 | * @return {Object} Current object.
456 | */
457 | initMenu() {
458 | const appMenu = Menu.buildFromTemplate([
459 | {
460 | label: 'Edit',
461 | submenu: [
462 | {role: 'undo'},
463 | {role: 'redo'},
464 | {role: 'cut'},
465 | {role: 'copy'},
466 | {role: 'paste'},
467 | {role: 'selectall'}
468 | ]
469 | }
470 | ]);
471 |
472 | let aboutMenuItem;
473 | if (isMac) {
474 | aboutMenuItem = {role: 'about'};
475 | } else {
476 | aboutMenuItem = {
477 | label: `About ${appName}`,
478 | click: () => {
479 | dialog.showMessageBox({
480 | title: `About ${appName}`,
481 | message: `${appName} ${app.getVersion()}`,
482 | detail: appCopyright,
483 | icon: appIconFilePath,
484 | buttons: []
485 | });
486 | }
487 | };
488 | }
489 |
490 | this._contextMenu = Menu.buildFromTemplate([
491 | {
492 | label: 'Open Most Recent Issue in Browser',
493 | click: () => {
494 | const notifier = this._notifiers[this._mostRecentNotifierIndex];
495 | shell.openExternal(`${notifier._settings.url}/issues/${notifier._mostRecentIssueId}`);
496 | notifierScreen.setNormalIcon();
497 | },
498 | enabled: false
499 | },
500 | {
501 | label: 'Preferences',
502 | click: () => {
503 | remote.getCurrentWindow().show();
504 | }
505 | },
506 | {
507 | type: 'separator'
508 | },
509 | aboutMenuItem,
510 | {
511 | type: 'separator'
512 | },
513 | {
514 | label: 'Quit',
515 | click: () => {
516 | const notifier = this._notifiers[this._currentNotifierIndex];
517 | if (!notifier._newFlag) {
518 | this.updateLastDisplayedNotifierIndex();
519 | }
520 |
521 | remote.app.quit();
522 | }
523 | }
524 | ]);
525 |
526 | Menu.setApplicationMenu(appMenu);
527 |
528 | this._tray = new Tray(this._iconFilePath);
529 | this._tray.setContextMenu(this._contextMenu);
530 |
531 | return this;
532 | }
533 |
534 | /**
535 | * Initialize the event listeners.
536 | * @return {Object} Current object.
537 | */
538 | initEventListener() {
539 | document.getElementById('save-button').addEventListener('click', () => {
540 | const notifier = this._notifiers[this._currentNotifierIndex];
541 | notifier.readScreenSettings();
542 |
543 | if (notifier.validateSettings()) {
544 | notifier.initFetch()
545 | .updateSettings();
546 | this.updateNotifierCount();
547 |
548 | if (notifier._newFlag) {
549 | this.updateLastDisplayedNotifierIndex();
550 | notifier.setNewFlag(false);
551 | }
552 |
553 | notie.alert({
554 | type: 'success',
555 | text: 'Settings have been saved.',
556 | time: notieDisplaySec
557 | });
558 | } else {
559 | notifier.readStoredSettings();
560 | }
561 | });
562 |
563 | document.getElementById('close-button').addEventListener('click', () => {
564 | const notifier = this._notifiers[this._currentNotifierIndex];
565 | notifier.readStoredSettings()
566 | .displaySettings();
567 | remote.getCurrentWindow().hide();
568 | });
569 |
570 | document.getElementById('test-connection-button').addEventListener('click', () => {
571 | const notifier = this._notifiers[this._currentNotifierIndex];
572 | notifier.testConnection(fetchMode.time);
573 | });
574 |
575 | document.getElementById('new-url-button').addEventListener('click', () => {
576 | const lastNotifier = this._notifiers[this._notifiers.length - 1];
577 | if (lastNotifier._settings.url === null) {
578 | return;
579 | }
580 |
581 | this._currentNotifierIndex = this._notifiers.length;
582 | this.addNotifier(this._currentNotifierIndex)
583 | .displaySettings();
584 | });
585 |
586 | document.getElementById('other-urls-button').addEventListener('click', () => {
587 | this.openURLMenu();
588 |
589 | if (this._notifiers.length === 0) {
590 | this.addNotifier(0);
591 | }
592 | });
593 |
594 | document.getElementById('delete-button').addEventListener('click', () => {
595 | notie.confirm({
596 | text: 'Are you sure you want to delete this setting?',
597 | cancelText: 'No',
598 | submitCallback: () => {
599 | this.deleteCurrentNotifierSettings()
600 | .resetAllSettings()
601 | .updateNotifierCount()
602 | .displaySettingsAfterDelete();
603 |
604 | notie.alert({
605 | type: 'success',
606 | text: 'Settings have been deleted.',
607 | time: notieDisplaySec
608 | });
609 | }
610 | });
611 | });
612 |
613 | return this;
614 | }
615 |
616 | /**
617 | * Display the default settings on the screen.
618 | * @return {Object} Current object.
619 | */
620 | displayDefaultSettings() {
621 | document.getElementById('default-fetch-interval-sec').innerHTML = defaultFetchIntervalSec;
622 | return this;
623 | }
624 |
625 | /**
626 | * Update the stored count of RedmineNotifier objects.
627 | * @return {Object} Current object.
628 | */
629 | updateNotifierCount() {
630 | localStorage.setItem('notifierCount', this._notifiers.length);
631 | return this;
632 | }
633 |
634 | /**
635 | * Update the stored index of last displayed RedmineNotifier object.
636 | * @return {Object} Current object.
637 | */
638 | updateLastDisplayedNotifierIndex() {
639 | localStorage.setItem('lastDisplayedNotifierIndex', this._currentNotifierIndex);
640 | return this;
641 | }
642 |
643 | /**
644 | * Add a RedmineNotifier object.
645 | * @param {number} index - Index of the object.
646 | * @return {Object} The RedmineNotifier object.
647 | */
648 | addNotifier(index) {
649 | const notifier = new RedmineNotifier(index);
650 | notifier.updateLastExecutionTime()
651 | .readStoredSettings()
652 | .setNewFlag(true);
653 | this._notifiers.push(notifier);
654 | return notifier;
655 | }
656 |
657 | /**
658 | * Select valid RedmineNotifier objects.
659 | * @return {RedmineNotifier[]} Valid RedmineNotifier objects.
660 | */
661 | selectValidNotifiers() {
662 | return this._notifiers.filter(notifier => {
663 | return notifier._settings.url !== null;
664 | });
665 | }
666 |
667 | /**
668 | * Open the URL menu.
669 | * @return {Object} Current object.
670 | */
671 | openURLMenu() {
672 | const choices = [];
673 |
674 | const notifiers = this.selectValidNotifiers();
675 | notifiers.forEach((notifier, index) => {
676 | choices.push({
677 | text: notifier.getStoredSetting('url'),
678 | handler: () => {
679 | this._currentNotifierIndex = index;
680 | this.updateLastDisplayedNotifierIndex();
681 | notifier.readStoredSettings()
682 | .displaySettings();
683 |
684 | this._notifiers = this.selectValidNotifiers();
685 | }
686 | });
687 | });
688 |
689 | notie.select({
690 | text: 'Stored URLs',
691 | choices
692 | });
693 |
694 | this.wrapURLMenuItems();
695 |
696 | return this;
697 | }
698 |
699 | /**
700 | * Wrap an HTMLElement around URL menu item elements.
701 | * @return {Object} Current object.
702 | */
703 | wrapURLMenuItems() {
704 | const selectContainer = document.createElement('div');
705 | selectContainer.className = 'notie-select-container';
706 |
707 | const selectChoices = Array.prototype.slice.call(document.getElementsByClassName('notie-select-choice'));
708 | const notieContainer = selectChoices[0].parentNode;
709 |
710 | selectChoices.forEach(choice => {
711 | selectContainer.appendChild(choice);
712 | });
713 |
714 | const {cancelButton} = notieContainer.getElementsByClassName('notie-background-neutral notie-button');
715 | notieContainer.insertBefore(selectContainer, cancelButton);
716 |
717 | return this;
718 | }
719 |
720 | /**
721 | * Delete the settings of current RedmineNotifier object.
722 | * @return {Object} Current object.
723 | */
724 | deleteCurrentNotifierSettings() {
725 | const notifier = this._notifiers[this._currentNotifierIndex];
726 | notifier.deleteStoredSettings()
727 | .readStoredSettings();
728 | return this;
729 | }
730 |
731 | /**
732 | * Reset all settings.
733 | * @return {Object} Current object.
734 | */
735 | resetAllSettings() {
736 | this._notifiers = this.selectValidNotifiers();
737 |
738 | localStorage.clear();
739 |
740 | this._notifiers.forEach((notifier, index) => {
741 | notifier._index = index;
742 | notifier.updateSettings();
743 | });
744 |
745 | return this;
746 | }
747 |
748 | /**
749 | * Display settings after deleting.
750 | * @return {Object} Current object.
751 | */
752 | displaySettingsAfterDelete() {
753 | if (this._notifiers.length === 0) {
754 | // Display the first settings
755 | this._currentNotifierIndex = 0;
756 | this.addNotifier(this._currentNotifierIndex);
757 | } else if (this._notifiers[this._currentNotifierIndex] === undefined) {
758 | // Display the previous settings
759 | this._currentNotifierIndex = this._currentNotifierIndex - 1;
760 | }
761 |
762 | const notifier = this._notifiers[this._currentNotifierIndex];
763 | notifier.displaySettings();
764 |
765 | return this;
766 | }
767 |
768 | /**
769 | * Set normal icon and disable "Open Most Recent Issue in Browser" in context menu.
770 | * @return {Object} Current object.
771 | */
772 | setNormalIcon() {
773 | this._tray.setImage(this._iconFilePath);
774 | this._contextMenu.items[0].enabled = false;
775 | this._mostRecentNotifierIndex = null;
776 | return this;
777 | }
778 |
779 | /**
780 | * Set notification icon and enable "Open Most Recent Issue in Browser" in context menu.
781 | * @param {number} index - Index of the most recent RedmineNotifier object.
782 | * @return {Object} Current object.
783 | */
784 | setNotificationIcon(index) {
785 | this._tray.setImage(this._notificationIconFilePath);
786 | this._contextMenu.items[0].enabled = true;
787 | this._mostRecentNotifierIndex = index;
788 | return this;
789 | }
790 | }
791 |
792 | /**
793 | * Check whether old settings exist.
794 | * @param {Object} oldSettings - Old settings.
795 | * @return {boolean} true if old settings exist.
796 | */
797 | const hasOldSettings = oldSettings => {
798 | for (const key in oldSettings) {
799 | if (Object.prototype.hasOwnProperty.call(oldSettings, key) && oldSettings[key] !== null) {
800 | return true;
801 | }
802 | }
803 |
804 | return false;
805 | };
806 |
807 | /**
808 | * Migrate the settings of Redmine Notifier 0.5.0 or 0.6.0.
809 | * @return {boolean} true if old settings are migrated.
810 | */
811 | const migrateOldSettings = () => {
812 | const oldSettings = {
813 | url: localStorage.getItem('url'),
814 | apiKey: localStorage.getItem('apiKey'),
815 | projectId: localStorage.getItem('projectId'),
816 | fetchIntervalSec: localStorage.getItem('fetchIntervalSec'),
817 | lastExecutionTime: localStorage.getItem('lastExecutionTime')
818 | };
819 |
820 | if (!hasOldSettings(oldSettings)) {
821 | return false;
822 | }
823 |
824 | // Copy to index 0
825 | const notifier = new RedmineNotifier(0);
826 | delete oldSettings.lastExecutionTime; // Strictly speaking, `lastExecutionTime` is not a setting
827 | notifier._settings = oldSettings;
828 | notifier.updateSettings();
829 | notifier.updateLastExecutionTime();
830 |
831 | localStorage.setItem('notifierCount', 1);
832 | localStorage.setItem('lastDisplayedNotifierIndex', 0);
833 |
834 | // Delete old settings
835 | localStorage.removeItem('url');
836 | localStorage.removeItem('apiKey');
837 | localStorage.removeItem('projectId');
838 | localStorage.removeItem('fetchIntervalSec');
839 | localStorage.removeItem('lastExecutionTime');
840 |
841 | return true;
842 | };
843 |
844 | window.addEventListener('load', () => {
845 | migrateOldSettings();
846 |
847 | notie.setOptions({
848 | classes: {
849 | selectChoice: 'notie-select-choice'
850 | }
851 | });
852 |
853 | const notifiers = [];
854 | const notifierCount = Number(localStorage.getItem('notifierCount'));
855 |
856 | for (let i = 0; i < notifierCount; i++) {
857 | const notifier = new RedmineNotifier(i);
858 | notifier.updateLastExecutionTime()
859 | .readStoredSettings();
860 |
861 | if (notifier.validateSettings()) {
862 | notifier.initFetch();
863 | }
864 |
865 | notifiers.push(notifier);
866 | }
867 |
868 | if (notifiers.length === 0) {
869 | const notifier = new RedmineNotifier(0);
870 | notifier.updateLastExecutionTime()
871 | .readStoredSettings();
872 | notifiers.push(notifier);
873 | }
874 |
875 | notifierScreen = new RedmineNotifierScreen();
876 | notifierScreen.initNotifiers(notifiers)
877 | .initMenu()
878 | .initEventListener()
879 | .displayDefaultSettings();
880 | });
881 | })();
882 |
883 |
--------------------------------------------------------------------------------
/app/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (() => {
4 | const {app, BrowserWindow} = require('electron');
5 |
6 | const isMac = process.platform === 'darwin';
7 |
8 | let win = null;
9 |
10 | app.on('window-all-closed', () => {
11 | if (!isMac) {
12 | app.quit();
13 | }
14 | });
15 |
16 | // Avoid the slow performance issue when renderer window is hidden
17 | app.commandLine.appendSwitch('disable-renderer-backgrounding');
18 |
19 | app.on('ready', () => {
20 | if (isMac) {
21 | app.dock.hide();
22 | }
23 |
24 | win = new BrowserWindow({
25 | width: 850,
26 | height: 300,
27 | show: false,
28 | frame: false,
29 | resizable: false,
30 | maximizable: false
31 | });
32 |
33 | win.loadFile(`${__dirname}/index.html`);
34 |
35 | win.on('closed', () => {
36 | win = null;
37 | });
38 | });
39 | })();
40 |
41 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redmine-notifier",
3 | "productName": "Redmine Notifier",
4 | "version": "0.8.0",
5 | "description": "Redmine Notifier",
6 | "main": "main.js",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/emsk/redmine-notifier.git"
10 | },
11 | "author": "emsk",
12 | "license": "MIT",
13 | "dependencies": {
14 | "node-notifier": "5.1.2",
15 | "notie": "4.3.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/stylesheets/font-awesome.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}
5 |
--------------------------------------------------------------------------------
/app/stylesheets/index.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'FontAwesome';
3 | font-style: normal;
4 | font-weight: normal;
5 | src: url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2');
6 | }
7 |
8 | @font-face {
9 | font-family: 'Montserrat-Regular';
10 | font-style: normal;
11 | font-weight: normal;
12 | src: url('../fonts/Montserrat-Regular.ttf') format('truetype');
13 | }
14 |
15 | ::selection {
16 | background-color: #afd7ff;
17 | }
18 |
19 | html {
20 | -webkit-app-region: drag;
21 | height: 100%;
22 | width: 100%;
23 | }
24 |
25 | body {
26 | background-color: #628db6;
27 | color: #fff;
28 | font-family: 'Montserrat-Regular';
29 | letter-spacing: 1px;
30 | margin: 0;
31 | }
32 |
33 | h1 {
34 | letter-spacing: 4px;
35 | text-align: center;
36 | }
37 |
38 | table {
39 | width: 100%;
40 | }
41 |
42 | th {
43 | text-align: left;
44 | width: 270px;
45 | }
46 |
47 | input {
48 | -webkit-app-region: no-drag;
49 | border: solid 1px #ccc;
50 | border-radius: 3px;
51 | font-size: 0.8em;
52 | height: 20px;
53 | padding: 0 3px;
54 | }
55 |
56 | input::-webkit-inner-spin-button,
57 | input::-webkit-outer-spin-button {
58 | -webkit-appearance: none;
59 | margin: 0;
60 | }
61 |
62 | input:focus {
63 | box-shadow: 0 0 0 1px #ccc;
64 | }
65 |
66 | button {
67 | -webkit-app-region: no-drag;
68 | background-color: #628db6;
69 | border: 3px solid #fff;
70 | border-radius: 50px;
71 | color: #fff;
72 | cursor: pointer;
73 | font: inherit;
74 | font-size: 0.6em;
75 | padding: 8px 20px;
76 | }
77 |
78 | input:focus,
79 | button:focus {
80 | outline: none;
81 | }
82 |
83 | button:hover {
84 | background-color: #3e5b76;
85 | }
86 |
87 | a {
88 | -webkit-app-region: no-drag;
89 | }
90 |
91 | #main {
92 | margin: 0 auto;
93 | width: 760px;
94 | }
95 |
96 | #setting-option {
97 | margin-top: 10px;
98 | }
99 |
100 | .setting-redmine {
101 | width: 350px;
102 | }
103 |
104 | .setting-redmine input {
105 | width: 340px;
106 | }
107 |
108 | .setting-number input {
109 | text-align: right;
110 | width: 60px;
111 | }
112 |
113 | .unit {
114 | font-size: 0.8em;
115 | }
116 |
117 | #test-connection-button {
118 | border: 2px solid #fff;
119 | font-size: 0.8em;
120 | margin-left: 5px;
121 | padding: 6px;
122 | }
123 |
124 | #save-button-area {
125 | font-size: 1.5em;
126 | margin-top: 30px;
127 | text-align: center;
128 | }
129 |
130 | #save-button {
131 | margin-right: 30px;
132 | }
133 |
134 | #sub-button-area {
135 | bottom: 35px;
136 | display: flex;
137 | position: absolute;
138 | right: 50px;
139 | }
140 |
141 | #sub-button-area button {
142 | border: 2px solid #fff;
143 | font-size: 16px;
144 | height: 32px;
145 | margin-left: 11px;
146 | width: 32px;
147 | }
148 |
149 | #other-urls-button {
150 | padding: 0 0 1px;
151 | }
152 |
153 | #new-url-button {
154 | padding: 4px;
155 | }
156 |
157 | #delete-button {
158 | padding: 0;
159 | }
160 |
161 | .notie-container {
162 | -webkit-app-region: no-drag;
163 | }
164 |
165 | .notie-textbox.notie-background-info {
166 | background-color: #3e5b76;
167 | }
168 |
169 | .notie-select-container {
170 | max-height: 184px;
171 | overflow: auto;
172 | }
173 |
174 | .notie-select-choice {
175 | background-color: #628db6;
176 | font-size: 0.8em;
177 | line-height: 1.7em;
178 | }
179 |
--------------------------------------------------------------------------------
/app/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | growly@^1.3.0:
6 | version "1.3.0"
7 | resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
8 |
9 | isexe@^1.1.1:
10 | version "1.1.2"
11 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0"
12 |
13 | node-notifier@5.1.2:
14 | version "5.1.2"
15 | resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff"
16 | dependencies:
17 | growly "^1.3.0"
18 | semver "^5.3.0"
19 | shellwords "^0.1.0"
20 | which "^1.2.12"
21 |
22 | notie@4.3.0:
23 | version "4.3.0"
24 | resolved "https://registry.yarnpkg.com/notie/-/notie-4.3.0.tgz#e805c64c7045ff29532714fad7a658f36464b37c"
25 |
26 | semver@^5.3.0:
27 | version "5.3.0"
28 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
29 |
30 | shellwords@^0.1.0:
31 | version "0.1.0"
32 | resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.0.tgz#66afd47b6a12932d9071cbfd98a52e785cd0ba14"
33 |
34 | which@^1.2.12:
35 | version "1.2.12"
36 | resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192"
37 | dependencies:
38 | isexe "^1.1.1"
39 |
--------------------------------------------------------------------------------
/bin/clean.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $(dirname $0)/..
3 | rm -rf ./node_modules ./dist ./app/node_modules
4 |
--------------------------------------------------------------------------------
/bin/lint-css.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $(dirname $0)/..
3 | $(yarn bin)/stylelint ./app/stylesheets/index.css || exit 0
4 |
--------------------------------------------------------------------------------
/bin/lint-js.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $(dirname $0)/..
3 | $(yarn bin)/xo || exit 0
4 |
--------------------------------------------------------------------------------
/bin/lint-text.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $(dirname $0)/..
3 | $(yarn bin)/textlint "./*.md" || exit 0
4 |
--------------------------------------------------------------------------------
/bin/pack-all.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $(dirname $0)/..
3 | $(yarn bin)/electron-builder --mac --win --x64 --publish never
4 |
5 | mv './dist/Redmine Notifier-0.8.0.dmg' ./dist/mac/RedmineNotifierSetup-0.8.0.dmg
6 | mv './dist/Redmine Notifier-0.8.0.dmg.blockmap' ./dist/mac
7 | mv ./dist/latest-mac.yml ./dist/mac
8 |
9 | mv './dist/squirrel-windows/Redmine Notifier Setup 0.8.0.exe' ./dist/squirrel-windows/RedmineNotifierSetup-0.8.0.exe
10 |
--------------------------------------------------------------------------------
/bin/pack-mac.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $(dirname $0)/..
3 | $(yarn bin)/electron-builder --mac --x64 --publish never
4 | mv './dist/Redmine Notifier-0.8.0.dmg' ./dist/mac/RedmineNotifierSetup-0.8.0.dmg
5 | mv './dist/Redmine Notifier-0.8.0.dmg.blockmap' ./dist/mac
6 | mv ./dist/latest-mac.yml ./dist/mac
7 |
--------------------------------------------------------------------------------
/bin/pack-win.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $(dirname $0)/..
3 | $(yarn bin)/electron-builder --win --x64 --publish never
4 | mv './dist/squirrel-windows/Redmine Notifier Setup 0.8.0.exe' ./dist/squirrel-windows/RedmineNotifierSetup-0.8.0.exe
5 |
--------------------------------------------------------------------------------
/bin/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $(dirname $0)/..
3 | if [ ! -e './dist/mac/Redmine Notifier.app' ]; then
4 | $(yarn bin)/electron-builder --mac --x64 --publish never
5 | fi
6 | $(yarn bin)/ava
7 |
--------------------------------------------------------------------------------
/bin/travis-build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | yarn test
3 |
--------------------------------------------------------------------------------
/build/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/build/background.png
--------------------------------------------------------------------------------
/build/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/build/icon.icns
--------------------------------------------------------------------------------
/build/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/build/icon.ico
--------------------------------------------------------------------------------
/build/install-spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/build/install-spinner.gif
--------------------------------------------------------------------------------
/examples/icon_osx_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/examples/icon_osx_normal.png
--------------------------------------------------------------------------------
/examples/icon_osx_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/examples/icon_osx_notification.png
--------------------------------------------------------------------------------
/examples/icon_win_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/examples/icon_win_normal.png
--------------------------------------------------------------------------------
/examples/icon_win_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/examples/icon_win_notification.png
--------------------------------------------------------------------------------
/examples/notification_osx_10.10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/examples/notification_osx_10.10.png
--------------------------------------------------------------------------------
/examples/notification_win_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/examples/notification_win_10.png
--------------------------------------------------------------------------------
/examples/redmine_notifier_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsk/redmine-notifier/677bb22d098128d3c22b26ca846419eff864093e/examples/redmine_notifier_settings.png
--------------------------------------------------------------------------------
/inch.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "included": [
4 | "app/**/*.js"
5 | ]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redmine-notifier",
3 | "version": "0.8.0",
4 | "scripts": {
5 | "start": "electron ./app/main.js",
6 | "clean": "./bin/clean.sh",
7 | "postinstall": "install-app-deps",
8 | "lint": "yarn run lint:js && yarn run lint:css && yarn run lint:text",
9 | "lint:js": "./bin/lint-js.sh",
10 | "lint:css": "./bin/lint-css.sh",
11 | "lint:text": "./bin/lint-text.sh",
12 | "pack": "./bin/pack-all.sh",
13 | "pack:mac": "./bin/pack-mac.sh",
14 | "pack:win": "./bin/pack-win.sh",
15 | "test": "./bin/test.sh",
16 | "release": "yarn run clean && yarn install && yarn run pack"
17 | },
18 | "devDependencies": {
19 | "ava": "~0.25.0",
20 | "electron": "2.0.0",
21 | "electron-builder": "20.11.0",
22 | "electron-builder-squirrel-windows": "20.11.0",
23 | "spectron": "~3.8.0",
24 | "stylelint": "~9.2.0",
25 | "stylelint-config-standard": "~18.2.0",
26 | "stylelint-order": "~0.8.1",
27 | "textlint": "^10.2.1",
28 | "textlint-rule-write-good": "^1.6.2",
29 | "xo": "~0.21.0"
30 | },
31 | "build": {
32 | "appId": "com.emsk.redmine-notifier",
33 | "asarUnpack": "{custom,images}/**/*",
34 | "copyright": "Copyright (c) 2015-2019 emsk",
35 | "mac": {
36 | "category": "public.app-category.productivity",
37 | "target": [
38 | "dmg"
39 | ]
40 | },
41 | "dmg": {
42 | "iconSize": 120,
43 | "contents": [
44 | {
45 | "x": 130,
46 | "y": 300,
47 | "type": "file"
48 | },
49 | {
50 | "x": 370,
51 | "y": 300,
52 | "type": "link",
53 | "path": "/Applications"
54 | }
55 | ]
56 | },
57 | "win": {
58 | "target": [
59 | "squirrel"
60 | ]
61 | }
62 | },
63 | "xo": {
64 | "envs": [
65 | "browser",
66 | "node"
67 | ],
68 | "space": true
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import test from 'ava';
4 | import {Application} from 'spectron';
5 |
6 | test.beforeEach(async t => {
7 | t.context.app = new Application({
8 | path: `${__dirname}/../dist/mac/Redmine Notifier.app/Contents/MacOS/Redmine Notifier`
9 | });
10 |
11 | await t.context.app.start();
12 | });
13 |
14 | test.afterEach.always(async t => {
15 | await t.context.app.stop();
16 | });
17 |
18 | test(async t => {
19 | const {app} = t.context;
20 | await app.client.waitUntilWindowLoaded();
21 |
22 | const {client} = app;
23 | t.is(await client.getWindowCount(), 1);
24 | t.regex(await client.getUrl(), /^file:\/\/.+\/index.html$/);
25 | t.is(await client.getTitle(), 'Preferences');
26 | t.is(await client.getText('h1:first-child'), 'Redmine Notifier');
27 |
28 | const win = app.browserWindow;
29 | const {width, height} = await win.getBounds();
30 | t.is(width, 850);
31 | t.is(height, 300);
32 |
33 | t.false(await win.isVisible());
34 | t.false(await win.isResizable());
35 | t.false(await win.isFocused());
36 | t.false(await win.isMaximized());
37 | t.false(await win.isMinimized());
38 | t.false(await win.isFullScreen());
39 | t.true(await win.isMovable());
40 | t.false(await win.isMaximizable());
41 | t.true(await win.isMinimizable());
42 | t.true(await win.isFullScreenable());
43 | t.true(await win.isClosable());
44 | t.false(await win.isAlwaysOnTop());
45 | t.false(await win.isKiosk());
46 | t.false(await win.isDocumentEdited());
47 | t.false(await win.isMenuBarAutoHide());
48 | t.true(await win.isMenuBarVisible());
49 | t.false(await win.isVisibleOnAllWorkspaces());
50 | t.false(await win.isDevToolsOpened());
51 | t.false(await win.isDevToolsFocused());
52 | });
53 |
54 |
--------------------------------------------------------------------------------