├── .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 | [![Build Status](https://travis-ci.org/emsk/redmine-notifier.svg?branch=main)](https://travis-ci.org/emsk/redmine-notifier) 4 | [![Code Climate](https://codeclimate.com/github/emsk/redmine-notifier/badges/gpa.svg)](https://codeclimate.com/github/emsk/redmine-notifier) 5 | [![Inline docs](http://inch-ci.org/github/emsk/redmine-notifier.svg?branch=main)](http://inch-ci.org/github/emsk/redmine-notifier) 6 | [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) 7 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](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 | ![Notification Mac 10.10](examples/notification_osx_10.10.png?raw=true) 24 | ![Notification Win 10](examples/notification_win_10.png?raw=true) 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: ![Icon Mac Normal](examples/icon_osx_normal.png?raw=true) Notification: ![Icon Mac Notification](examples/icon_osx_notification.png?raw=true) 34 | 35 | ##### Tray Icon (Windows) 36 | 37 | Normal: ![Icon Win Normal](examples/icon_win_normal.png?raw=true) Notification: ![Icon Win Notification](examples/icon_win_notification.png?raw=true) 38 | 39 | ## Settings 40 | 41 | Open the context menu and select "Preferences". 42 | 43 | ![Settings](examples/redmine_notifier_settings.png?raw=true) 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 | 15 | 16 | 20 | 21 | 26 | 27 | 28 | 32 | 33 | 34 | 35 | 39 | 40 | 41 |
17 | 18 | Redmine URL (required) 19 | 22 | 25 |
29 | 30 | API key (required) 31 |
36 | 37 | Project ID 38 |
42 | 43 | 44 | 45 | 49 | 53 | 54 |
46 | 47 | Fetch interval time 48 | 50 | 51 | sec (default: ) 52 |
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 | --------------------------------------------------------------------------------