├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README-ja.md ├── README.md ├── app ├── config.js ├── index.js ├── login.js ├── menu.js ├── package.json ├── renderer │ ├── index.html │ ├── login.css │ ├── login.html │ ├── login.js │ ├── main.js │ ├── preload.js │ ├── search.css │ ├── searcher.js │ └── style.css ├── static │ ├── tray.png │ └── tray@2x.png ├── tray.js ├── updater.js └── utils.js ├── build ├── entitlements.mac.plist ├── icon.icns ├── icon.ico └── icon.png ├── package-lock.json ├── package.json └── scripts └── notarize.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: egoist 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build/release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v** 7 | branches: 8 | - master 9 | 10 | jobs: 11 | release: 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | matrix: 16 | os: [macos-latest, ubuntu-latest, windows-latest] 17 | 18 | steps: 19 | - name: Check out Git repository 20 | uses: actions/checkout@v1 21 | 22 | - name: Install Node.js, NPM and Yarn 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 12 26 | 27 | - name: Install dependencies 28 | run: npm install 29 | 30 | - name: Test 31 | run: npm test 32 | 33 | - name: Build/release Electron app 34 | uses: samuelmeuli/action-electron-builder@v1 35 | with: 36 | # GitHub token, automatically provided to the action 37 | # (No need to define this secret in the repo settings) 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | # If the commit is tagged with a version (e.g. "v1.0.0"), 41 | # release the app after building 42 | release: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.head_commit.message, '/release') }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | dist/ 5 | app/yarn.lock 6 | app/package-lock.json 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | "@egoist/prettier-config" 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) egoist <0x142857@gmail.com> (https://egoistian.com) 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 | -------------------------------------------------------------------------------- /README-ja.md: -------------------------------------------------------------------------------- 1 | # DevDocs Desktop 2 | 3 | [![version](https://img.shields.io/github/release/egoist/devdocs-desktop.svg?style=flat-square)](https://github.com/egoist/devdocs-desktop/releases) 4 | [![downloads](https://img.shields.io/github/downloads/egoist/devdocs-desktop/total.svg?style=flat-square)](https://github.com/egoist/devdocs-desktop/releases) 5 | [![downloads latest](https://img.shields.io/github/downloads/egoist/devdocs-desktop/latest/total.svg?style=flat-square)](https://github.com/egoist/devdocs-desktop/releases/latest) 6 | [![travis](https://img.shields.io/travis/egoist/devdocs-desktop.svg?style=flat-square)](https://travis-ci.org/egoist/devdocs-desktop) 7 | [![appveyor](https://img.shields.io/appveyor/ci/egoist/devdocs-desktop.svg?style=flat-square)](https://ci.appveyor.com/project/egoist/devdocs-desktop) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat-square)](https://github.com/egoist/donate) [![chat](https://img.shields.io/badge/chat-on%20discord-7289DA.svg?style=flat-square)](https://chat.egoist.moe) 8 | 9 | 10 | [DevDocs.io](https://devdocs.io/)は多数のAPIドキュメントを素早く便利にまとめて、探すことのできるインターフェイスです。このデスクトップアプリは非公式です。 11 | 12 | ![devdocs-preview](https://user-images.githubusercontent.com/8784712/27121730-11676ba8-511b-11e7-8c01-00444ee8501a.png) 13 | 14 | ## 特徴 15 | 16 | ### バックグラウンドで動作 17 | 18 | 19 | ウィンドウを閉じたとき、アプリはmacOSのdockやLinux/Windowsのトレイにバックグラウンドで動作し続けます。dockやトレイでアイコンを右クリックすると、アプリの終了を選択できます。 20 | macOSではdockでアイコンをクリックすると、ウィンドウが表示されます。 21 | Linuxでは、トレイでアイコンを右クリックすると、ウィンドウの切り替えを選択できます。 22 | Windowsでは、トレイのアイコンをクリックすると、ウィンドウを切り替えます。 23 | 24 | 25 | 26 | ### ショートカットキー 27 | 28 | 29 | `devdocs` のウェブサイトは素晴らしいショートカットキーをサポートしています。アプリの`help`ページを確認してみましょう。 30 | 31 | help 32 | 33 | ### グローバルショートカット 34 | 35 | 36 | Ctrl+Shift+D (macOSではCommand+Shift+D)を押すと、アプリを切り替えします。 37 | 38 | ## ダウンロード 39 | 40 | 41 | 最新リリースのダウンロードは 42 | [こちら](https://github.com/egoist/devdocs-desktop/releases) 43 | 44 | ## 開発 45 | 46 | このアプリの開発はとても簡単です。Webpackのようなビルドツールは必要ありません。 47 | [./app](/app) をチェックしてください 48 | 49 | ```bash 50 | npm install 51 | 52 | npm run app 53 | # edit files, save, refresh and it's done. 54 | ``` 55 | 56 | ## 配布 57 | 58 | ```bash 59 | npm run dist 60 | ``` 61 | 62 | ## ライセンス 63 | 64 | MIT © [EGOIST](https://github.com/egoist) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevDocs Desktop 2 | 3 | 日本語説明ページは[こちら](https://github.com/egoist/devdocs-desktop/blob/master/README-ja.md) 4 | 5 | [![version](https://img.shields.io/github/release/egoist/devdocs-desktop.svg?style=flat-square)](https://github.com/egoist/devdocs-desktop/releases) 6 | [![downloads](https://img.shields.io/github/downloads/egoist/devdocs-desktop/total.svg?style=flat-square)](https://github.com/egoist/devdocs-desktop/releases) 7 | [![downloads latest](https://img.shields.io/github/downloads/egoist/devdocs-desktop/latest/total.svg?style=flat-square)](https://github.com/egoist/devdocs-desktop/releases/latest) 8 | 9 | [DevDocs.io](https://devdocs.io/) combines multiple API documentations in a fast, organized, and searchable interface. This is an unofficial desktop app for it. 10 | 11 | ![devdocs-preview](https://user-images.githubusercontent.com/8784712/27121730-11676ba8-511b-11e7-8c01-00444ee8501a.png) 12 | 13 | 14 | 15 | ## Features 16 | 17 | ### Background behavior 18 | 19 | When closing the window, the app will continue running in the background, in the dock on macOS and the tray on Linux/Windows. Right-click the dock/tray icon and choose Quit to completely quit the app. On macOS, click the dock icon to show the window. On Linux, right-click the tray icon and choose Toggle to toggle the window. On Windows, click the tray icon to toggle the window. 20 | 21 | ### Build-in shortcuts 22 | 23 | `devdocs` the website itself has great built-in shortcuts support, just check the `help` page in the app. 24 | 25 | help 26 | 27 | ## Planned features 28 | 29 | Please consider [sponsoring me](http://github.com/sponsors/egoist) to accelerate development. 30 | 31 | - Menubar mode: switch beween desktop mode and menubar mode 32 | - Tabs support: allow to open documentation in a new tab 33 | 34 | ### Global shortcut 35 | 36 | Use Ctrl+Shift+D (or Command+Shift+D on macOS) to toggle the app. 37 | 38 | ## Install 39 | 40 | ### Using Homebrew 41 | 42 | ``` 43 | brew install --cask devdocs 44 | ``` 45 | 46 | ### Manual download 47 | 48 | You can manually download the latest release [here](https://github.com/egoist/devdocs-desktop/releases). 49 | 50 | ## Development 51 | 52 | It's really easy to develop this app, no build tools like Webpack needed here, checkout [./app](/app) to get more: 53 | 54 | ```bash 55 | npm install 56 | 57 | npm run app 58 | # edit files, save, refresh and it's done. 59 | ``` 60 | 61 | ## Distribute 62 | 63 | ```bash 64 | npm run dist 65 | ``` 66 | 67 | ## License 68 | 69 | MIT © [EGOIST](https://github.com/egoist) 70 | -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | const Config = require('electron-store') 2 | 3 | module.exports = new Config({ 4 | defaults: { 5 | lastWindowState: { 6 | width: 800, 7 | height: 600 8 | }, 9 | shortcut: { 10 | toggleApp: null 11 | }, 12 | mode: 'dark' 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { app, BrowserWindow, Menu } = require('electron') 3 | const debug = require('debug')('devdocs-desktop:index') 4 | const createMenu = require('./menu') 5 | const config = require('./config') 6 | const tray = require('./tray') 7 | const updater = require('./updater') 8 | const { toggleGlobalShortcut } = require('./utils') 9 | const login = require('./login') 10 | 11 | require('electron-debug')() 12 | require('electron-context-menu')({ 13 | showInspectElement: true, 14 | }) 15 | 16 | app.setAppUserModelId('sh.egoist.devdocs') 17 | 18 | let mainWindow 19 | let isQuitting = false 20 | let urlToOpen 21 | 22 | if (!app.requestSingleInstanceLock()) { 23 | app.quit() 24 | } 25 | 26 | app.on('second-instance', () => { 27 | if (mainWindow) { 28 | if (mainWindow.isMinimized()) { 29 | mainWindow.restore() 30 | } 31 | 32 | mainWindow.show() 33 | } 34 | }) 35 | 36 | function toggleWindow() { 37 | if (mainWindow.isVisible()) { 38 | mainWindow.hide() 39 | } else { 40 | mainWindow.show() 41 | } 42 | } 43 | 44 | function createMainWindow() { 45 | const lastWindowState = config.get('lastWindowState') 46 | 47 | const win = new BrowserWindow({ 48 | title: app.name, 49 | x: lastWindowState.x, 50 | y: lastWindowState.y, 51 | width: lastWindowState.width, 52 | height: lastWindowState.height, 53 | minWidth: 600, 54 | minHeight: 400, 55 | show: false, 56 | titleBarStyle: process.platform === 'darwin' ? 'hidden' : 'default', 57 | trafficLightPosition: { 58 | x: 10, 59 | y: 10, 60 | }, 61 | webPreferences: { 62 | nodeIntegration: true, 63 | webviewTag: true, 64 | contextIsolation: false, 65 | enableRemoteModule: true, 66 | }, 67 | }) 68 | 69 | if (process.platform === 'darwin') { 70 | win.setSheetOffset(24) 71 | } 72 | 73 | win.loadFile(path.join(__dirname, 'renderer/index.html')) 74 | 75 | win.on('close', e => { 76 | if (!isQuitting) { 77 | e.preventDefault() 78 | 79 | if (process.platform === 'darwin') { 80 | app.hide() 81 | } else { 82 | win.hide() 83 | } 84 | } 85 | }) 86 | 87 | return win 88 | } 89 | 90 | app.on('login', (event, webContents, request, authInfo, cb) => { 91 | debug('app.on(login)') 92 | event.preventDefault() 93 | login(cb) 94 | }) 95 | 96 | app.on('ready', () => { 97 | const shortcut = config.get('shortcut') 98 | for (const name in shortcut) { 99 | const accelerator = shortcut[name] 100 | if (accelerator) { 101 | toggleGlobalShortcut({ 102 | name, 103 | accelerator, 104 | registered: false, 105 | action: toggleWindow, 106 | }) 107 | } 108 | } 109 | 110 | Menu.setApplicationMenu( 111 | createMenu({ 112 | toggleWindow, 113 | }), 114 | ) 115 | mainWindow = createMainWindow() 116 | tray.create(mainWindow) 117 | 118 | mainWindow.once('ready-to-show', () => { 119 | mainWindow.show() 120 | updater.init() 121 | if (urlToOpen) { 122 | mainWindow.webContents.send('link', urlToOpen) 123 | } 124 | }) 125 | }) 126 | 127 | app.on('activate', () => { 128 | mainWindow.show() 129 | }) 130 | 131 | // I have no idea what I'm doing here 132 | // It just works... 133 | let hasOpenedOnce 134 | app.on('browser-window-focus', () => { 135 | if (hasOpenedOnce) { 136 | mainWindow.webContents.send('focus-webview') 137 | } else { 138 | hasOpenedOnce = true 139 | } 140 | }) 141 | 142 | app.on('before-quit', () => { 143 | isQuitting = true 144 | 145 | if (!mainWindow.isFullScreen()) { 146 | config.set('lastWindowState', mainWindow.getBounds()) 147 | } 148 | }) 149 | 150 | app.setAsDefaultProtocolClient('devdocs') 151 | 152 | app.on('will-finish-launching', () => { 153 | app.on('open-url', (e, url) => { 154 | if (mainWindow) { 155 | mainWindow.webContents.send('link', url) 156 | } else { 157 | urlToOpen = url 158 | } 159 | }) 160 | }) 161 | -------------------------------------------------------------------------------- /app/login.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow, ipcMain } = require('electron') 2 | const path = require('path') 3 | const debug = require('debug')('devdocs-desktop:login') 4 | 5 | module.exports = cb => { 6 | debug('Login launching...') 7 | const loginWindow = new BrowserWindow({ 8 | width: 300, 9 | height: 300, 10 | frame: false, 11 | resizable: false 12 | }) 13 | loginWindow.loadURL(`file://${path.join(__dirname, '/renderer/login.html')}`) 14 | 15 | ipcMain.once('login-message', (event, usernameAndPassword) => { 16 | debug('Login message recieved', usernameAndPassword[0]) 17 | cb(usernameAndPassword[0], usernameAndPassword[1]) 18 | loginWindow.close() 19 | }) 20 | return loginWindow 21 | } 22 | -------------------------------------------------------------------------------- /app/menu.js: -------------------------------------------------------------------------------- 1 | const { 2 | Menu, 3 | shell, 4 | globalShortcut, 5 | BrowserWindow, 6 | dialog 7 | } = require('electron') 8 | const axios = require('axios') 9 | const semverCompare = require('semver-compare') 10 | const { configDir, toggleGlobalShortcut } = require('./utils') 11 | const config = require('./config') 12 | const pkg = require('./package') 13 | 14 | function sendAction(action, ...args) { 15 | const [win] = BrowserWindow.getAllWindows() 16 | 17 | if (process.platform === 'darwin') { 18 | win.restore() 19 | } 20 | 21 | win.webContents.send(action, ...args) 22 | } 23 | 24 | function updateMenu(opts) { 25 | Menu.setApplicationMenu(createMenu(opts)) 26 | } 27 | 28 | function createMenu(opts) { 29 | const toggleAppAccelerator = 30 | config.get('shortcut.toggleApp') || 'CmdOrCtrl+Shift+D' 31 | const toggleAppAcceleratorRegistered = globalShortcut.isRegistered( 32 | toggleAppAccelerator 33 | ) 34 | 35 | const preferences = [ 36 | { 37 | label: 'Preferences', 38 | submenu: [ 39 | { 40 | label: 'Custom CSS', 41 | click() { 42 | shell.openItem(configDir('custom.css')) 43 | } 44 | }, 45 | { 46 | label: 'Custom JS', 47 | click() { 48 | shell.openItem(configDir('custom.js')) 49 | } 50 | }, 51 | { 52 | label: `${ 53 | toggleAppAcceleratorRegistered ? 'Disable' : 'Enable' 54 | } Global Shortcut`, 55 | click() { 56 | toggleGlobalShortcut({ 57 | name: 'toggleApp', 58 | registered: toggleAppAcceleratorRegistered, 59 | accelerator: toggleAppAccelerator, 60 | action: opts.toggleWindow 61 | }) 62 | updateMenu(opts) 63 | } 64 | } 65 | ] 66 | }, 67 | { 68 | type: 'separator' 69 | } 70 | ] 71 | 72 | const checkForUpdates = { 73 | label: 'Check for Updates', 74 | async click(item, focusedWindow) { 75 | if (!focusedWindow) return 76 | 77 | const api = 78 | 'https://api.github.com/repos/egoist/devdocs-desktop/releases/latest' 79 | const latest = await axios.get(api).then(res => res.data) 80 | 81 | if (semverCompare(latest.tag_name.slice(1), pkg.version) === 1) { 82 | dialog.showMessageBox( 83 | focusedWindow, 84 | { 85 | type: 'info', 86 | message: 'New updates!', 87 | detail: `A new release (${latest.tag_name}) is available, view more details?`, 88 | buttons: ['OK', 'Cancel'], 89 | defaultId: 0 90 | }, 91 | selected => { 92 | if (selected === 0) { 93 | shell.openExternal( 94 | 'https://github.com/egoist/devdocs-desktop/releases/latest' 95 | ) 96 | } 97 | } 98 | ) 99 | } else { 100 | dialog.showMessageBox(focusedWindow, { 101 | message: 'No updates!', 102 | detail: `v${pkg.version} is already the latest version.` 103 | }) 104 | } 105 | } 106 | } 107 | 108 | const template = [ 109 | { 110 | label: 'Edit', 111 | submenu: [ 112 | { 113 | role: 'undo' 114 | }, 115 | { 116 | role: 'redo' 117 | }, 118 | { 119 | type: 'separator' 120 | }, 121 | { 122 | role: 'cut' 123 | }, 124 | { 125 | role: 'copy' 126 | }, 127 | { 128 | role: 'paste' 129 | }, 130 | { 131 | role: 'pasteandmatchstyle' 132 | }, 133 | { 134 | role: 'delete' 135 | }, 136 | { 137 | role: 'selectall' 138 | } 139 | ] 140 | }, 141 | { 142 | label: 'View', 143 | submenu: [ 144 | { 145 | label: 'Reset Text Size', 146 | accelerator: 'CmdOrCtrl+0', 147 | click() { 148 | sendAction('zoom-reset') 149 | } 150 | }, 151 | { 152 | label: 'Increase Text Size', 153 | accelerator: 'CmdOrCtrl+Plus', 154 | click() { 155 | sendAction('zoom-in') 156 | } 157 | }, 158 | { 159 | label: 'Decrease Text Size', 160 | accelerator: 'CmdOrCtrl+-', 161 | click() { 162 | sendAction('zoom-out') 163 | } 164 | }, 165 | { 166 | type: 'separator' 167 | }, 168 | { 169 | label: 'Search In Page', 170 | accelerator: 'CmdOrCtrl+F', 171 | click(item, focusedWindow) { 172 | focusedWindow.webContents.send('open-search') 173 | } 174 | }, 175 | { 176 | type: 'separator' 177 | }, 178 | { 179 | label: 'Reload', 180 | accelerator: 'CmdOrCtrl+R', 181 | click(item, focusedWindow) { 182 | if (focusedWindow) focusedWindow.reload() 183 | } 184 | }, 185 | { 186 | label: 'Toggle Developer Tools', 187 | accelerator: 188 | process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', 189 | click(item, focusedWindow) { 190 | if (focusedWindow) focusedWindow.webContents.toggleDevTools() 191 | } 192 | } 193 | ] 194 | }, 195 | { 196 | role: 'window', 197 | submenu: [ 198 | { 199 | role: 'minimize' 200 | }, 201 | { 202 | role: 'close' 203 | } 204 | ] 205 | } 206 | ] 207 | 208 | if (process.platform === 'darwin') { 209 | template.unshift({ 210 | label: 'DevDocs', 211 | submenu: [ 212 | { 213 | role: 'about' 214 | }, 215 | checkForUpdates, 216 | { 217 | type: 'separator' 218 | }, 219 | ...preferences, 220 | { 221 | role: 'services', 222 | submenu: [] 223 | }, 224 | { 225 | type: 'separator' 226 | }, 227 | { 228 | role: 'hide' 229 | }, 230 | { 231 | role: 'hideothers' 232 | }, 233 | { 234 | role: 'unhide' 235 | }, 236 | { 237 | type: 'separator' 238 | }, 239 | { 240 | role: 'quit' 241 | } 242 | ] 243 | }) 244 | // Edit menu. 245 | template[1].submenu.push( 246 | { 247 | type: 'separator' 248 | }, 249 | { 250 | label: 'Speech', 251 | submenu: [ 252 | { 253 | role: 'startspeaking' 254 | }, 255 | { 256 | role: 'stopspeaking' 257 | } 258 | ] 259 | } 260 | ) 261 | // Window menu. 262 | template[3].submenu = [ 263 | { 264 | label: 'Close', 265 | accelerator: 'CmdOrCtrl+W', 266 | role: 'close' 267 | }, 268 | { 269 | label: 'Minimize', 270 | accelerator: 'CmdOrCtrl+M', 271 | role: 'minimize' 272 | }, 273 | { 274 | label: 'Zoom', 275 | role: 'zoom' 276 | }, 277 | { 278 | type: 'separator' 279 | }, 280 | { 281 | label: 'Bring All to Front', 282 | role: 'front' 283 | } 284 | ] 285 | } else { 286 | template.push({ 287 | label: 'Preferences', 288 | submenu: [...preferences[0].submenu, preferences[1], checkForUpdates] 289 | }) 290 | } 291 | 292 | template.push({ 293 | role: 'help', 294 | submenu: [ 295 | { 296 | label: 'Report Issues', 297 | click() { 298 | shell.openExternal( 299 | 'http://github.com/egoist/devdocs-desktop/issues/new' 300 | ) 301 | } 302 | } 303 | ] 304 | }) 305 | 306 | return Menu.buildFromTemplate(template) 307 | } 308 | 309 | module.exports = createMenu 310 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DevDocs", 3 | "productName": "DevDocs", 4 | "version": "0.7.2", 5 | "description": "Desktop client for devdocs.io", 6 | "author": { 7 | "name": "EGOIST", 8 | "email": "0x142857@gmail.com", 9 | "url": "https://github.com/egoist" 10 | }, 11 | "license": "MIT", 12 | "main": "index.js", 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/egoist/devdocs-desktop.git" 19 | }, 20 | "keywords": [ 21 | "devdocs", 22 | "app", 23 | "electron", 24 | "desktop" 25 | ], 26 | "bugs": { 27 | "url": "https://github.com/egoist/devdocs-desktop/issues" 28 | }, 29 | "homepage": "https://github.com/egoist/devdocs-desktop#readme", 30 | "dependencies": { 31 | "axios": "^0.21.1", 32 | "debug": "^4.3.1", 33 | "electron-context-menu": "^2.5.1", 34 | "electron-debug": "^3.0.1", 35 | "electron-is-dev": "^2.0.0", 36 | "electron-log": "^4.3.4", 37 | "electron-store": "^7.0.3", 38 | "electron-updater": "^4.3.8", 39 | "mkdirp": "^1.0.4", 40 | "semver-compare": "^1.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Loading DevDocs... 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/renderer/login.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | font-family: Verdana, sans-serif; 4 | } 5 | 6 | html, body { 7 | height: 100%; 8 | margin: 0; 9 | -webkit-app-region: drag; 10 | background: #fff; 11 | } 12 | .login-form { 13 | height: 100%; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .login-form > div { 21 | text-align: center; 22 | } 23 | 24 | .login-form .input-div { 25 | margin: 5px; 26 | } 27 | 28 | .login-form > div > input { 29 | display: block; 30 | height: 30px; 31 | width: 250px; 32 | margin: 10px; 33 | padding: 10px; 34 | text-align: center; 35 | font-size: 16px; 36 | -webkit-app-region: no-drag; 37 | } 38 | 39 | .login-form > div > button { 40 | border: 0; 41 | font-size: 15px; 42 | font-weight: 100; 43 | text-transform: uppercase; 44 | height: 50px; 45 | width: 274px; 46 | background-color: #2196F3; 47 | color: white; 48 | -webkit-app-region: no-drag; 49 | } 50 | 51 | button:hover { 52 | background-color: #0D47A1; 53 | } 54 | -------------------------------------------------------------------------------- /app/renderer/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login Prompt 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/renderer/login.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron') 2 | 3 | document.querySelector('#login-form').addEventListener('submit', event => { 4 | event.preventDefault() 5 | ipcRenderer.send('login-message', [ 6 | document.querySelector('#username-input').value, 7 | document.querySelector('#password-input').value 8 | ]) 9 | }) 10 | -------------------------------------------------------------------------------- /app/renderer/main.js: -------------------------------------------------------------------------------- 1 | const os = require('os') 2 | const fs = require('fs') 3 | const { ipcRenderer: ipc, remote, shell } = require('electron') 4 | const mkdirp = require('mkdirp') 5 | const contextMenu = require('electron-context-menu') 6 | const { configDir } = require('../utils') 7 | const config = require('../config') 8 | const Searcher = require('./searcher') 9 | 10 | const win = remote.getCurrentWindow() 11 | let webview // eslint-disable-line prefer-const 12 | 13 | function ensureCustomFiles() { 14 | mkdirp.sync(configDir()) 15 | const css = configDir('custom.css') 16 | const js = configDir('custom.js') 17 | if (!fs.existsSync(css)) { 18 | fs.writeFileSync(css, '', 'utf8') 19 | } 20 | 21 | if (!fs.existsSync(js)) { 22 | fs.writeFileSync(js, '', 'utf8') 23 | } 24 | } 25 | 26 | function createHeader() { 27 | const header = document.createElement('header') 28 | header.className = 'header' 29 | header.innerHTML = '

Loading DevDocs...

' 30 | header.addEventListener('dblclick', () => { 31 | win.maximize() 32 | }) 33 | header.addEventListener('click', () => { 34 | webview.focus() 35 | }) 36 | 37 | document.body.append(header) 38 | } 39 | 40 | function createWebView() { 41 | // Create webview 42 | const webview = document.createElement('webview') 43 | webview.className = 'webview' 44 | webview.src = 'https://devdocs.io' 45 | webview.preload = 'preload.js' 46 | document.body.append(webview) 47 | 48 | // Initialize in-page searcher 49 | const searcher = new Searcher(webview) 50 | 51 | searcher.on('close', () => { 52 | webview.focus() 53 | }) 54 | 55 | ipc.on('open-search', () => { 56 | searcher.open() 57 | }) 58 | 59 | ipc.on('zoom-in', () => { 60 | webview.send('zoom-in') 61 | }) 62 | 63 | ipc.on('zoom-out', () => { 64 | webview.send('zoom-out') 65 | }) 66 | 67 | ipc.on('zoom-reset', () => { 68 | webview.send('zoom-reset') 69 | }) 70 | 71 | ipc.on('focus-webview', () => { 72 | webview.focus() 73 | }) 74 | 75 | ipc.on('link', (e, url) => { 76 | const route = url.replace('devdocs://', '') 77 | const SEACH_RE = /^search\/(.+)$/ 78 | if (SEACH_RE.test(route)) { 79 | const keyword = SEACH_RE.exec(route)[1] 80 | webview.src = `https://devdocs.io/#q=${keyword}` 81 | } 82 | }) 83 | 84 | webview.addEventListener('ipc-message', e => { 85 | if (e.channel === 'switch-mode') { 86 | const [mode] = e.args 87 | config.set('mode', mode) 88 | if (mode === 'dark') { 89 | document.body.classList.add('is-dark-mode') 90 | } else { 91 | document.body.classList.remove('is-dark-mode') 92 | } 93 | } 94 | }) 95 | 96 | webview.addEventListener('dom-ready', () => { 97 | // Insert custom css 98 | const css = ` 99 | ._app button:focus { 100 | outline: none; 101 | } 102 | ._app button._search-clear { 103 | top: .5rem; 104 | } 105 | ` 106 | webview.insertCSS(css + fs.readFileSync(configDir('custom.css'), 'utf8')) 107 | webview.executeJavaScript(fs.readFileSync(configDir('custom.js'), 'utf8')) 108 | webview.focus() 109 | // Add context menus 110 | contextMenu({ 111 | window: webview, 112 | showInspectElement: true, 113 | append(props) { 114 | const hasText = 115 | props.selectionText && props.selectionText.trim().length > 0 116 | return [ 117 | { 118 | id: 'searchGoogle', 119 | label: 'Search in Google', 120 | enabled: hasText, 121 | visible: hasText, 122 | click() { 123 | shell.openExternal( 124 | `https://www.google.com/search?q=${props.selectionText}` 125 | ) 126 | } 127 | }, 128 | { 129 | id: 'searchDuck', 130 | label: 'Search in DuckDuckGo', 131 | enabled: hasText, 132 | visible: hasText, 133 | click() { 134 | shell.openExternal( 135 | `https://duckduckgo.com/?q=${props.selectionText}` 136 | ) 137 | } 138 | } 139 | ] 140 | } 141 | }) 142 | }) 143 | 144 | // Update app title 145 | webview.addEventListener('did-stop-loading', () => { 146 | const title = webview.getTitle() 147 | win.setTitle(title) 148 | document.querySelector('#title').textContent = title 149 | }) 150 | 151 | webview.addEventListener('new-window', e => { 152 | e.preventDefault() 153 | shell.openExternal(e.url) 154 | }) 155 | 156 | return webview 157 | } 158 | 159 | ensureCustomFiles() 160 | createHeader() 161 | webview = createWebView() 162 | 163 | document.body.classList.add(`is-${os.platform()}`) 164 | 165 | if (config.get('mode') === 'dark') { 166 | document.body.classList.add('is-dark-mode') 167 | } 168 | -------------------------------------------------------------------------------- /app/renderer/preload.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer: ipc } = require('electron') 2 | const config = require('../config') 3 | 4 | document.addEventListener('change', e => { 5 | if (e.target.name === 'dark') { 6 | switchMode(e.target.checked) 7 | } 8 | }) 9 | 10 | switchMode(/dark=1;/.test(document.cookie)) 11 | 12 | function switchMode(isDark) { 13 | const mode = isDark ? 'dark' : 'light' 14 | ipc.sendToHost('switch-mode', mode) 15 | } 16 | 17 | function setZoom(zoomFactor) { 18 | const node = document.querySelector('#zoomFactor') 19 | node.textContent = `body {zoom: ${zoomFactor} !important}` 20 | config.set('zoomFactor', zoomFactor) 21 | } 22 | 23 | document.addEventListener('DOMContentLoaded', () => { 24 | const zoomFactor = config.get('zoomFactor') || 1 25 | const style = document.createElement('style') 26 | style.id = 'zoomFactor' 27 | document.body.append(style) 28 | setZoom(zoomFactor) 29 | }) 30 | 31 | ipc.on('zoom-in', () => { 32 | const zoomFactor = config.get('zoomFactor') + 0.1 33 | 34 | if (zoomFactor < 1.6) { 35 | setZoom(zoomFactor) 36 | } 37 | }) 38 | 39 | ipc.on('zoom-out', () => { 40 | const zoomFactor = config.get('zoomFactor') - 0.1 41 | 42 | if (zoomFactor >= 0.8) { 43 | setZoom(zoomFactor) 44 | } 45 | }) 46 | 47 | ipc.on('zoom-reset', () => { 48 | setZoom(1) 49 | }) 50 | -------------------------------------------------------------------------------- /app/renderer/search.css: -------------------------------------------------------------------------------- 1 | .searcher { 2 | position: fixed; 3 | top: var(--webHeaderHeight); 4 | right: 0; 5 | padding: 10px; 6 | background: var(--headerBackground); 7 | border: 1px solid var(--searchBorder); 8 | border-top: none; 9 | border-right: none; 10 | display: flex; 11 | border-radius: 0 0 0 3px; 12 | z-index: 9999; 13 | } 14 | 15 | .is-darwin .searcher { 16 | top: calc(24px + var(--webHeaderHeight)); 17 | } 18 | 19 | .searcher-progress { 20 | height: 30px; 21 | display: inline-flex; 22 | align-items: center; 23 | margin-right: 5px; 24 | justify-content: center; 25 | min-width: 34px; 26 | } 27 | 28 | .searcher-action { 29 | border: 1px solid var(--searchActionBorder); 30 | background: var(--contentBackgorund); 31 | outline: none; 32 | padding: 0 8px; 33 | cursor: pointer; 34 | font-size: .875rem; 35 | height: 30px; 36 | overflow: hidden; 37 | color: var(--textColor); 38 | } 39 | 40 | .searcher-prev { 41 | border-radius: 3px 0 0 3px; 42 | } 43 | 44 | .searcher-close { 45 | border-radius: 0 3px 3px 0; 46 | } 47 | 48 | .searcher-action:not(:last-child) { 49 | border-right: none; 50 | } 51 | 52 | .searcher-action svg { 53 | width: 14px; 54 | height: 14px; 55 | } 56 | 57 | .searcher-action:hover { 58 | background: var(--searchActionHoverBackground); 59 | } 60 | 61 | .searcher-input { 62 | border: 1px solid var(--searchInputBorder); 63 | border-radius: 3px; 64 | padding: 0 5px; 65 | outline: none; 66 | font-size: .875rem; 67 | height: 30px; 68 | margin-right: 5px; 69 | width: 200px; 70 | background-color: var(--contentBackground); 71 | color: var(--searchInputColor); 72 | } 73 | 74 | .searcher-input:focus { 75 | border-color: var(--inputFocusBorder); 76 | box-shadow: 0 0 1px var(--inputFocusBorder); 77 | } 78 | 79 | .searcher__hidden { 80 | display: none; 81 | } 82 | 83 | .searcher-progress__disabled { 84 | display: none; 85 | } 86 | -------------------------------------------------------------------------------- /app/renderer/searcher.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events') 2 | 3 | module.exports = class Searcher extends EventEmitter { 4 | constructor(target) { 5 | super() 6 | this.target = target 7 | } 8 | 9 | toggle() { 10 | return this.opened ? this.close() : this.open() 11 | } 12 | 13 | open() { 14 | if (!this.initialized) this.initialize() 15 | this.opened = true 16 | this.$searcher.classList.remove('searcher__hidden') 17 | this.$input.focus() 18 | this.$input.select() 19 | this.emit('open') 20 | } 21 | 22 | close() { 23 | this.opened = false 24 | this.target.stopFindInPage('clearSelection') 25 | this.hideSearcher() 26 | this.emit('close') 27 | } 28 | 29 | initialize() { 30 | this.initialized = true 31 | const $wrapper = document.createElement('div') 32 | $wrapper.innerHTML = ` 33 |
34 | 35 | 36 | 41 | 46 | 51 |
52 | ` 53 | document.body.append($wrapper) 54 | this.$searcher = $wrapper.querySelector('.searcher') 55 | this.$progress = this.$searcher.querySelector('.searcher-progress') 56 | this.$input = this.$searcher.querySelector('.searcher-input') 57 | this.$input.addEventListener('keydown', e => { 58 | if (e.key === 'Enter') { 59 | // Enter 60 | this.findNext(e.target.value) 61 | } else if (e.key === 'Escape') { 62 | // Esc 63 | this.close() 64 | } 65 | }) 66 | this.$prev = this.$searcher.querySelector('.searcher-prev') 67 | this.$next = this.$searcher.querySelector('.searcher-next') 68 | this.$close = this.$searcher.querySelector('.searcher-close') 69 | this.$prev.addEventListener('click', () => { 70 | this.findPrev(this.$input.value) 71 | }) 72 | this.$next.addEventListener('click', () => { 73 | this.findNext(this.$input.value) 74 | }) 75 | this.$close.addEventListener('click', () => { 76 | this.close() 77 | }) 78 | 79 | this.target.addEventListener('found-in-page', e => { 80 | const { matches, activeMatchOrdinal } = e.result 81 | 82 | this.showProgress(activeMatchOrdinal, matches) 83 | this.$input.focus() 84 | }) 85 | this.emit('initialized') 86 | } 87 | 88 | findNext(value, opts) { 89 | if (value) { 90 | this.target.findInPage(value, opts) 91 | } 92 | 93 | return this 94 | } 95 | 96 | findPrev(value, opts) { 97 | if (value) { 98 | this.target.findInPage( 99 | value, 100 | Object.assign( 101 | { 102 | forward: false 103 | }, 104 | opts 105 | ) 106 | ) 107 | } 108 | 109 | return this 110 | } 111 | 112 | showProgress(current, total) { 113 | this.$progress.classList.remove('searcher-progress__disabled') 114 | this.$progress.textContent = `${current}/${total}` 115 | } 116 | 117 | hideSearcher() { 118 | this.$progress.classList.add('searcher-progress__disabled') 119 | this.$searcher.classList.add('searcher__hidden') 120 | this.$input.value = '' 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/renderer/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --textColor: #333; 3 | --inputFocusBorder: #35b5f4 !important; 4 | --headerBackground: #eee; 5 | --webHeaderHeight: 3rem; 6 | --searchBorder: #ccc; 7 | --searchInputBorder: #ccc; 8 | --searchActionBorder: #ccc; 9 | --searchActionHoverBackground: #ccc; 10 | --contentBackground: white; 11 | } 12 | 13 | @media (prefers-color-scheme: dark) { 14 | :root { 15 | --textColor: #ccc; 16 | --headerColor: #999; 17 | --headerBackground: #1c1c1c; 18 | --searchBorder: black; 19 | --searchInputColor: white; 20 | --searchInputBorder: black; 21 | --contentBackground: #33373a; 22 | --searchActionBorder: black; 23 | --searchActionHoverBackground: #333; 24 | } 25 | } 26 | 27 | html, body { 28 | height: 100%; 29 | } 30 | 31 | body { 32 | margin: 0; 33 | font: 14px/1.4 -apple-system, 34 | BlinkMacSystemFont, 35 | "San Francisco", 36 | "Segoe UI", 37 | Roboto, 38 | Ubuntu, 39 | "Helvetica Neue", 40 | Arial, 41 | sans-serif; 42 | color: var(--textColor); 43 | background-color: var(--contentBackground); 44 | } 45 | 46 | * { 47 | box-sizing: border-box; 48 | } 49 | 50 | .webview { 51 | height: 100%; 52 | width: 100%; 53 | border: none; 54 | } 55 | 56 | .webview:focus { 57 | outline: none; 58 | } 59 | 60 | .header { 61 | height: 24px; 62 | display: flex; 63 | align-items: center; 64 | justify-content: center; 65 | font-size: 12px; 66 | color: var(--headerColor); 67 | background-color: var(--headerBackground); 68 | -webkit-app-region: drag; 69 | user-select: none; 70 | } 71 | 72 | .header .app-title { 73 | font-size: 12px; 74 | font-weight: 400; 75 | margin: 0; 76 | } 77 | 78 | .is-darwin .header { 79 | display: flex; 80 | } 81 | 82 | .is-darwin .webview { 83 | height: calc(100% - 24px); 84 | } 85 | -------------------------------------------------------------------------------- /app/static/tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/devdocs-desktop/791786035527b7cf8d5e8335cc5ec506fdd20e4d/app/static/tray.png -------------------------------------------------------------------------------- /app/static/tray@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/devdocs-desktop/791786035527b7cf8d5e8335cc5ec506fdd20e4d/app/static/tray@2x.png -------------------------------------------------------------------------------- /app/tray.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { app, Menu, Tray } = require('electron') 3 | 4 | let tray = null 5 | 6 | exports.create = win => { 7 | if (process.platform === 'darwin' || tray) { 8 | return 9 | } 10 | 11 | const iconPath = path.join(__dirname, 'static/tray.png') 12 | 13 | const toggleWin = () => { 14 | if (win.isVisible()) { 15 | win.hide() 16 | } else { 17 | win.show() 18 | } 19 | } 20 | 21 | const contextMenu = Menu.buildFromTemplate([ 22 | { 23 | label: 'Toggle', 24 | click() { 25 | toggleWin() 26 | } 27 | }, 28 | { 29 | type: 'separator' 30 | }, 31 | { 32 | role: 'quit' 33 | } 34 | ]) 35 | 36 | tray = new Tray(iconPath) 37 | tray.setToolTip(`${app.getName()}`) 38 | tray.setContextMenu(contextMenu) 39 | tray.on('click', toggleWin) 40 | } 41 | -------------------------------------------------------------------------------- /app/updater.js: -------------------------------------------------------------------------------- 1 | const isDev = require('electron-is-dev') 2 | const log = require('electron-log') 3 | const { autoUpdater } = require('electron-updater') 4 | 5 | exports.init = () => { 6 | if (isDev || process.platform === 'linux') { 7 | return 8 | } 9 | 10 | autoUpdater.logger = log 11 | autoUpdater.logger.transports.file.level = 'info' 12 | autoUpdater.checkForUpdatesAndNotify() 13 | } 14 | -------------------------------------------------------------------------------- /app/utils.js: -------------------------------------------------------------------------------- 1 | const os = require('os') 2 | const path = require('path') 3 | const { globalShortcut } = require('electron') 4 | const config = require('./config') 5 | 6 | const home = os.homedir() 7 | 8 | exports.configDir = function (...args) { 9 | return path.join(home, '.devdocs', ...args) 10 | } 11 | 12 | exports.toggleGlobalShortcut = function ({ 13 | name, 14 | registered, 15 | accelerator, 16 | action 17 | }) { 18 | if (registered) { 19 | globalShortcut.unregister(accelerator) 20 | config.delete(`shortcut.${name}`) 21 | } else { 22 | const ret = globalShortcut.register(accelerator, action) 23 | config.set(`shortcut.${name}`, accelerator) 24 | if (!ret) { 25 | console.error(`Failed to register ${accelerator}`) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/devdocs-desktop/791786035527b7cf8d5e8335cc5ec506fdd20e4d/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/devdocs-desktop/791786035527b7cf8d5e8335cc5ec506fdd20e4d/build/icon.ico -------------------------------------------------------------------------------- /build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/devdocs-desktop/791786035527b7cf8d5e8335cc5ec506fdd20e4d/build/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "devdocs-desktop", 4 | "version": "0.0.0-this-does-not-matter", 5 | "description": "Desktop client for devdocs.io", 6 | "author": { 7 | "name": "EGOIST", 8 | "email": "0x142857@gmail.com", 9 | "url": "https://github.com/egoist" 10 | }, 11 | "license": "MIT", 12 | "main": "app/index.js", 13 | "scripts": { 14 | "postinstall": "electron-builder install-app-deps", 15 | "test": "npm run lint", 16 | "lint": "xo", 17 | "app": "DEBUG=devdocs-desktop:* electron app/index.js", 18 | "pack": "electron-builder --dir", 19 | "dist": "electron-builder", 20 | "release": "electron-builder" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/egoist/devdocs-desktop.git" 25 | }, 26 | "keywords": [ 27 | "devdocs", 28 | "app", 29 | "electron" 30 | ], 31 | "bugs": { 32 | "url": "https://github.com/egoist/devdocs-desktop/issues" 33 | }, 34 | "homepage": "https://github.com/egoist/devdocs-desktop#readme", 35 | "devDependencies": { 36 | "@egoist/prettier-config": "^0.1.0", 37 | "dotenv": "^8.2.0", 38 | "electron": "^12.0.4", 39 | "electron-builder": "^22.10.5", 40 | "electron-devtools-installer": "^3.2.0", 41 | "electron-notarize": "^1.0.0", 42 | "eslint-config-rem": "^4.0.0", 43 | "minimist": "^1.2.0", 44 | "prettier": "^1.19.1", 45 | "xo": "^0.27.2" 46 | }, 47 | "xo": { 48 | "extends": "rem", 49 | "envs": [ 50 | "browser" 51 | ], 52 | "rules": { 53 | "guard-for-in": 0, 54 | "max-params": [ 55 | "error", 56 | 5 57 | ], 58 | "import/order": "off", 59 | "comma-dangle": "off" 60 | } 61 | }, 62 | "build": { 63 | "appId": "sh.egoist.devdocs", 64 | "productName": "DevDocs", 65 | "compression": "maximum", 66 | "asar": true, 67 | "dmg": { 68 | "sign": false 69 | }, 70 | "mac": { 71 | "category": "public.app-category.developer-tools", 72 | "hardenedRuntime": true, 73 | "gatekeeperAssess": false, 74 | "entitlements": "build/entitlements.mac.plist", 75 | "entitlementsInherit": "build/entitlements.mac.plist" 76 | }, 77 | "win": { 78 | "target": [ 79 | "nsis", 80 | "zip", 81 | "portable" 82 | ] 83 | }, 84 | "nsis": { 85 | "oneClick": false 86 | }, 87 | "linux": { 88 | "synopsis": "DevDocs desktop app", 89 | "category": "Development", 90 | "target": [ 91 | "AppImage", 92 | "deb", 93 | "tar.xz" 94 | ] 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /scripts/notarize.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const { notarize } = require('electron-notarize') 3 | 4 | module.exports = context => { 5 | const { electronPlatformName, appOutDir } = context 6 | if (electronPlatformName !== 'darwin') { 7 | return 8 | } 9 | 10 | const appName = context.packager.appInfo.productFilename 11 | 12 | return notarize({ 13 | appBundleId: 'sh.egoist.devdocs', 14 | appPath: `${appOutDir}/${appName}.app`, 15 | appleId: process.env.APPLEID, 16 | appleIdPassword: process.env.APPLEIDPASS 17 | }) 18 | } 19 | --------------------------------------------------------------------------------