├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── build ├── background.png ├── background@2x.png ├── entitlements.mas.plist ├── icon.icns ├── icon.ico └── icon.png ├── code-of-conduct.md ├── contributing.md ├── docs ├── _config.yml ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── highres-icon.png ├── index.html ├── media │ ├── black-theme.png │ ├── compact-mode.png │ ├── custom-shortcut-keys.gif │ ├── dark-theme.png │ ├── export-notes.gif │ ├── focus-mode.png │ ├── header.png │ ├── logo.png │ ├── note-navigation.gif │ ├── print-note.gif │ ├── scalable-interface.gif │ ├── sepia-theme.png │ └── yinxiang-support.png └── update.json ├── index.js ├── license.md ├── package.json ├── readme.md ├── src ├── browser.js ├── config.js ├── configs │ ├── darwin.js │ ├── index.js │ ├── linux.js │ └── win32.js ├── dialog.js ├── file.js ├── keymap.js ├── menu │ ├── app.js │ ├── edit.js │ ├── file.js │ ├── format.js │ ├── help.js │ ├── index.js │ ├── tray.js │ ├── view.js │ └── window.js ├── mode.js ├── nav.js ├── pdf.js ├── save.js ├── settings.js ├── startup.js ├── style │ ├── black-mode.css │ ├── browser.css │ ├── dark-mode.css │ └── sepia-mode.css ├── time.js ├── tray.js ├── update.js ├── url.js ├── util.js └── win.js ├── static ├── Icon.icns ├── Icon.ico ├── Icon.png ├── IconTray.png └── IconTray@2x.png └── yarn.lock /.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 13 | 14 | [{*.json, *.yml}] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | *.psd binary 4 | *.pfd binary 5 | *.ai binary 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your problem. 18 | 19 | **Technical Info (please complete the following information)** 20 | - OS: 21 | - Tusk Version: 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Additional context** 14 | Add any other context or screenshots about the feature request here. 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | package-json.lock 4 | 5 | # logs 6 | *.log 7 | 8 | # OS 9 | .DS_Store 10 | 11 | # IDE 12 | .vscode 13 | .idea 14 | *.swp 15 | *.swo 16 | 17 | # compiled 18 | dist 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - os: osx 4 | osx_image: xcode10.2 5 | language: node_js 6 | node_js: "12" 7 | env: 8 | - ELECTRON_CACHE=$HOME/.cache/electron 9 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 10 | 11 | - os: linux 12 | services: docker 13 | language: generic 14 | 15 | cache: 16 | directories: 17 | - node_modules 18 | - $HOME/.cache/electron 19 | - $HOME/.cache/electron-builder 20 | 21 | script: 22 | - | 23 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then 24 | docker run --rm \ 25 | --env-file <(env | grep -v '\r' | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \ 26 | -v ${PWD}:/project \ 27 | -v ~/.cache/electron:/root/.cache/electron \ 28 | -v ~/.cache/electron-builder:/root/.cache/electron-builder \ 29 | electronuserland/builder:wine \ 30 | /bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn release --linux --win" 31 | else 32 | yarn release 33 | fi 34 | 35 | before_cache: 36 | - rm -rf $HOME/.cache/electron-builder/wine 37 | 38 | branches: 39 | except: 40 | - "/^v\\d+\\.\\d+\\.\\d+$/" 41 | -------------------------------------------------------------------------------- /build/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/build/background.png -------------------------------------------------------------------------------- /build/background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/build/background@2x.png -------------------------------------------------------------------------------- /build/entitlements.mas.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.files.user-selected.read-write 12 | 13 | com.apple.security.files.downloads.read-write 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/build/icon.ico -------------------------------------------------------------------------------- /build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/build/icon.png -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at klaussinani@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Tusk ❤ 2 | 3 | Thank you for taking the time to contribute to Tusk! ✨🎉 4 | 5 | Please note that this project is released with a [Contributor Code of Conduct](code-of-conduct.md). By participating in this project you agree to abide by its terms. 6 | 7 | ## How to contribute! 8 | 9 | ### Improve documentation 10 | 11 | Typo corrections, error fixes, better explanations, more examples etc. Open an issue regarding anything that you think it could be improved! You can use the [`docs` label](https://github.com/klaussinani/tusk/labels/docs) to find out what others have suggested! 12 | 13 | ### Improve issues 14 | 15 | Sometimes reported issues lack information, are not reproducible, or are even plain invalid. Help us out to make them easier to resolve. Handling issues takes a lot of time that we could rather spend on fixing bugs and adding features. 16 | 17 | ### Give feedback on issues 18 | 19 | We're always looking for more opinions on discussions in the issue tracker. It's a good opportunity to influence the future direction of the project. 20 | 21 | The [`question` label](https://github.com/klaussinani/tusk/labels/question) is a good place to find ongoing discussions. 22 | 23 | ### Write code 24 | 25 | You can use issue labels to discover issues you could help us out with! 26 | 27 | - [`enhancement` issues](https://github.com/klaussinani/tusk/labels/enhancement) are features we are open to including 28 | - [`bug` issues](https://github.com/klaussinani/tusk/labels/bug) are known bugs we would like to fix 29 | - [`needs testing` issues](https://github.com/klaussinani/tusk/labels/needs%20testing) are features that we are still working on improving 30 | - [`future` issues](https://github.com/klaussinani/tusk/labels/future) are those that we'd like to get to, but not anytime soon. Please check before working on these since we may not yet want to take on the burden of supporting those features 31 | - on the [`help wanted`](https://github.com/klaussinani/tusk/labels/future) label you can always find something exciting going on 32 | 33 | You may find an issue is assigned, or has the [`assigned` label](https://github.com/klaussinani/tusk/labels/assigned). Please double-check before starting on this issue because somebody else is likely already working on it 34 | 35 | ### Say hi! 36 | 37 | Come over and say hi anytime you feel like on [Gitter](https://gitter.im/klaussinani/tusk). 38 | 39 | ### Translating Documentation 40 | 41 | - Make sure that the document is not already translated in that language. 42 | - Add the name of the language to the document as an extension, e.g: `readme.JP.md` 43 | - Place the translated document inside the [`docs`](https://github.com/klaussinani/tusk/tree/master/docs) directory. 44 | - Create a Pull Request including the language in the title, e.g: `Readme: Japanese Translation` 45 | 46 | ### Submitting an issue 47 | 48 | - Search the issue tracker before opening an issue 49 | - Ensure you're using the latest version of Tusk: ![Latest version](https://badge.fury.io/gh/klaussinani%2Ftusk.svg) 50 | - Use a descriptive title 51 | - Include as much information as possible; 52 | - Steps to reproduce the issue 53 | - App settings 54 | - Error message 55 | - Tusk version 56 | - Operating system **etc** 57 | 58 | ### Submitting a pull request 59 | 60 | - Non-trivial changes are often best discussed in an issue first, to prevent you from doing unnecessary work 61 | - Try making the pull request from a [topic branch](https://github.com/dchelimsky/rspec/wiki/Topic-Branches) if it is of crucial importance 62 | - Use a descriptive title for the pull request and commits 63 | - You might be asked to do changes to your pull request, you can do that by just [updating the existing one](https://github.com/RichardLitt/docs/blob/master/amending-a-commit-guide.md) 64 | 65 | > Inspired by project [AVA](https://github.com/avajs/ava/blob/master/contributing.md)'s contributing.md 66 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | include: [".well-known", "update.json"] 2 | auto: true 3 | -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/favicon-96x96.png -------------------------------------------------------------------------------- /docs/highres-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/highres-icon.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tusk - Refined Evernote desktop app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 207 | 217 | 218 | 219 | 220 |
221 |
222 | 223 |

Tusk

224 |
225 |
Refined Evernote desktop app
226 |
227 | 228 |
229 |
230 |
231 |
232 | Download 233 | Fork Source 234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |

What's inside?

243 |
244 |
245 |
246 |

70+ Keyboard shortcuts

247 |
248 |
Toggle anything in a flash.
249 | View all. 250 |
251 | 252 |
253 |
254 |
255 |
256 |

Sepia Theme

257 |
258 |
Perfect for glossy screens.
259 |
260 | 261 |
262 |
263 |
264 |
265 |

Dark Theme

266 |
267 |
Silky & relaxing.
268 |
269 | 270 |
271 |
272 |
273 |
274 |

Black Theme

275 |
276 |
Deep focus materialized.
277 |
278 | 279 |
280 |
281 |
282 |
283 |

Focus Mode

284 |
285 |
Immerse yourself in a distraction-free note-taking mode.
286 |
287 | 288 |
289 |
290 |
291 |
292 |

Compact Mode

293 |
294 |
Keep your productivity to the maximum no matter the screen size.
295 |
296 | 297 |
298 |
299 |
300 |
301 |

Note Printing

302 |
303 |
Transfering your notes from screen to paper is only a keystroke away.
304 |
305 | 306 |
307 |
308 |
309 |
310 |

Note Navigation

311 |
312 |
Navigate seamlessly without taking your hands away from the keyboard.
313 |
314 | 315 |
316 |
317 |
318 |
319 |

Custom Shortcut Keys

320 |
321 |
Adjust Tusk to your workflow by modifying any shortcut key to your own preference.
322 |
323 | 324 |
325 |
326 |
327 |
328 |

Export Notes

329 |
330 |
Export and save your notes effortlessly on your machine as `pdf` files.
331 |
332 | 333 |
334 |
335 |
336 |
337 |
338 |

Yinxiang Support

339 |
340 |
One-click login to your Yinxiang account.
341 |
342 | 343 |
344 |
345 |
346 |
347 |
348 |

Scalable Interface

349 |
350 |
Adjust the zooming factor to your own preference.
351 |
352 | 353 |
354 |
355 |
356 |
357 |

Cross Platform, Update Notifications & more

358 |
359 |
View all the features in detail on Github.
360 |
361 |
362 |
363 |
364 | 374 | 395 | 396 | 397 | 398 | -------------------------------------------------------------------------------- /docs/media/black-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/black-theme.png -------------------------------------------------------------------------------- /docs/media/compact-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/compact-mode.png -------------------------------------------------------------------------------- /docs/media/custom-shortcut-keys.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/custom-shortcut-keys.gif -------------------------------------------------------------------------------- /docs/media/dark-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/dark-theme.png -------------------------------------------------------------------------------- /docs/media/export-notes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/export-notes.gif -------------------------------------------------------------------------------- /docs/media/focus-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/focus-mode.png -------------------------------------------------------------------------------- /docs/media/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/header.png -------------------------------------------------------------------------------- /docs/media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/logo.png -------------------------------------------------------------------------------- /docs/media/note-navigation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/note-navigation.gif -------------------------------------------------------------------------------- /docs/media/print-note.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/print-note.gif -------------------------------------------------------------------------------- /docs/media/scalable-interface.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/scalable-interface.gif -------------------------------------------------------------------------------- /docs/media/sepia-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/sepia-theme.png -------------------------------------------------------------------------------- /docs/media/yinxiang-support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/docs/media/yinxiang-support.png -------------------------------------------------------------------------------- /docs/update.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tusk v0.23.0", 3 | "url": "https://github.com/klaussinani/tusk/releases/latest", 4 | "version": "0.23.0" 5 | } 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {app, BrowserWindow, ipcMain, Menu, shell} = require('electron'); 3 | const fs = require('fs'); 4 | const {is, formatTitle, formatURL, formatYinxiangURL, readSheet} = require('./src/util'); 5 | const file = require('./src/file'); 6 | const menu = require('./src/menu'); 7 | const pdf = require('./src/pdf'); 8 | const settings = require('./src/settings'); 9 | const shortcut = require('./src/keymap'); 10 | const time = require('./src/time'); 11 | const tray = require('./src/tray'); 12 | const update = require('./src/update'); 13 | const url = require('./src/url'); 14 | const win = require('./src/win'); 15 | 16 | const {log} = console; 17 | 18 | require('electron-debug')({enabled: true}); 19 | require('electron-dl')(); 20 | require('electron-context-menu')(); 21 | 22 | let exiting = false; 23 | let mainWindow; 24 | 25 | if (!app.requestSingleInstanceLock()) { 26 | app.quit(); 27 | } 28 | 29 | app.on('second-instance', () => { 30 | if (mainWindow) { 31 | if (mainWindow.isMinimized()) { 32 | mainWindow.restore(); 33 | } 34 | 35 | mainWindow.show(); 36 | } 37 | }); 38 | 39 | function createMainWindow() { 40 | const lastURL = settings.get('useYinxiang') ? url.yinxiang : url.evernote; 41 | 42 | const tuskWindow = new BrowserWindow(win.defaultOpts); 43 | 44 | tuskWindow.loadURL(lastURL); 45 | 46 | tuskWindow.on('close', e => { 47 | if (!exiting) { 48 | e.preventDefault(); 49 | 50 | if (is.darwin) { 51 | app.hide(); 52 | } else { 53 | tuskWindow.hide(); 54 | } 55 | } 56 | }); 57 | 58 | tuskWindow.on('page-title-updated', e => { 59 | e.preventDefault(); 60 | }); 61 | 62 | tuskWindow.on('unresponsive', log); 63 | 64 | tuskWindow.webContents.on('did-navigate-in-page', (_, url) => { 65 | settings.set('lastURL', url); 66 | }); 67 | 68 | return tuskWindow; 69 | } 70 | 71 | app.on('ready', () => { 72 | Menu.setApplicationMenu(menu); 73 | mainWindow = createMainWindow(); 74 | 75 | if (settings.get('useGlobalShortcuts')) { 76 | shortcut.registerGlobal(); 77 | } 78 | 79 | if (!settings.get('hideTray')) { 80 | tray.create(); 81 | } 82 | 83 | const {webContents} = mainWindow; 84 | 85 | webContents.on('dom-ready', () => { 86 | const stylesheets = fs.readdirSync(file.style); 87 | stylesheets.forEach(x => webContents.insertCSS(readSheet(x))); 88 | 89 | if (settings.get('launchMinimized')) { 90 | mainWindow.minimize(); 91 | } else { 92 | mainWindow.show(); 93 | } 94 | }); 95 | 96 | webContents.on('new-window', (e, url) => { 97 | e.preventDefault(); 98 | url = settings.get('useYinxiang') ? formatYinxiangURL(url) : formatURL(url); 99 | 100 | if (is.downloadURL(url)) { 101 | webContents.downloadURL(url); 102 | } else { 103 | shell.openExternal(url); 104 | } 105 | }); 106 | 107 | webContents.on('crashed', log); 108 | 109 | if (!settings.get('disableAutoUpdateCheck')) { 110 | setInterval(() => update.auto(), time.ms(settings.get('updateCheckPeriod'))); 111 | } 112 | }); 113 | 114 | ipcMain.on('print-to-pdf', pdf.print); 115 | 116 | ipcMain.on('export-as-pdf', e => { 117 | const {webContents} = mainWindow; 118 | pdf.save(e, formatTitle(webContents.getTitle())); 119 | }); 120 | 121 | process.on('uncaughtException', log); 122 | 123 | app.on('activate', () => mainWindow.show()); 124 | 125 | app.on('before-quit', () => { 126 | exiting = true; 127 | if (!mainWindow.isFullScreen()) { 128 | settings.set('lastWindowState', mainWindow.getBounds()); 129 | } 130 | }); 131 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - present Klaus Sinani (klaussinani.github.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tusk", 3 | "productName": "Tusk", 4 | "version": "0.23.0", 5 | "description": "Refined Evernote desktop app", 6 | "license": "MIT", 7 | "repository": "klaussinani/tusk", 8 | "author": { 9 | "name": "Klaus Sinani", 10 | "email": "klaussinani@gmail.com", 11 | "url": "https://klaussinani.github.io" 12 | }, 13 | "maintainers": [ 14 | { 15 | "name": "Mario Sinani", 16 | "email": "mariosinani@protonmail.ch", 17 | "url": "https://github.com/mariosinani" 18 | } 19 | ], 20 | "scripts": { 21 | "postinstall": "electron-builder install-app-deps", 22 | "test": "xo && stylelint 'src/style/*.css'", 23 | "release": "build --publish always", 24 | "start": "electron ." 25 | }, 26 | "dependencies": { 27 | "auto-launch": "^5.0.1", 28 | "decode-uri-component": "^0.2.0", 29 | "electron-context-menu": "^0.9.0", 30 | "electron-debug": "^1.3.0", 31 | "electron-dl": "^1.11.0", 32 | "electron-settings": "^3.1.4", 33 | "turndown": "^5.0.1" 34 | }, 35 | "devDependencies": { 36 | "electron": "6.0.11", 37 | "electron-builder": "22.9.1", 38 | "stylelint": "^9.9.0", 39 | "xo": "*" 40 | }, 41 | "xo": { 42 | "envs": [ 43 | "browser", 44 | "node" 45 | ], 46 | "rules": { 47 | "quote-props": 0 48 | }, 49 | "space": 2 50 | }, 51 | "stylelint": { 52 | "rules": { 53 | "block-closing-brace-empty-line-before": "never", 54 | "block-closing-brace-newline-after": "always", 55 | "block-no-empty": true, 56 | "block-opening-brace-space-before": "always", 57 | "color-hex-case": "upper", 58 | "color-hex-length": "long", 59 | "color-no-invalid-hex": true, 60 | "comment-no-empty": true, 61 | "declaration-block-semicolon-space-before": "never", 62 | "indentation": 2, 63 | "max-empty-lines": 0, 64 | "no-duplicate-selectors": true 65 | } 66 | }, 67 | "build": { 68 | "appId": "com.klaussinani.tusk", 69 | "files": [ 70 | "**/*", 71 | "!media${/*}", 72 | "!docs${/*}" 73 | ], 74 | "linux": { 75 | "category": "Office", 76 | "description": "Tusk is an unofficial, featureful, open source, community-driven, free Evernote app used by people in more than 130 countries.", 77 | "synopsis": "Refined Evernote desktop app", 78 | "target": [ 79 | { 80 | "target": "AppImage", 81 | "arch": [ 82 | "ia32", 83 | "x64" 84 | ] 85 | }, 86 | { 87 | "target": "deb", 88 | "arch": [ 89 | "ia32", 90 | "x64" 91 | ] 92 | }, 93 | { 94 | "target": "pacman", 95 | "arch": [ 96 | "ia32", 97 | "x64" 98 | ] 99 | }, 100 | { 101 | "target": "rpm", 102 | "arch": [ 103 | "ia32", 104 | "x64" 105 | ] 106 | }, 107 | { 108 | "target": "snap", 109 | "arch": [ 110 | "x64" 111 | ] 112 | } 113 | ] 114 | }, 115 | "nsis": { 116 | "license": "license.md" 117 | }, 118 | "snap": { 119 | "grade": "stable", 120 | "confinement": "strict" 121 | }, 122 | "win": { 123 | "target": [ 124 | { 125 | "target": "nsis", 126 | "arch": [ 127 | "ia32", 128 | "x64" 129 | ] 130 | }, 131 | { 132 | "target": "portable", 133 | "arch": [ 134 | "ia32", 135 | "x64" 136 | ] 137 | } 138 | ] 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 |
Tusk 3 |

4 | 5 |

6 | Refined Evernote desktop app 7 |

8 | 9 |
10 | 11 | Tusk 12 | 13 |
14 | 15 |

16 | 17 | Build Status 18 | 19 |

20 | 21 |
22 |
23 | Sponsored by: 24 |
25 | 26 |
27 | Better Stack 28 |
29 | 30 | Spot, Resolve, and Prevent Downtime. 31 | 32 |
33 |
34 | 35 | ## Description 36 | 37 | Tusk is an unofficial, featureful, open source, community-driven, free Evernote app used by people in more than [140 countries](https://snapcraft.io/tusk). 38 | 39 | Tusk is [indicated by Evernote](https://help.evernote.com/hc/en-us/articles/208313748-Evernote-on-Linux) as an alternative client for Linux environments trusted by the open source community. 40 | 41 | You can now support the development process through [GitHub Sponsors](https://github.com/sponsors/klaussinani). 42 | 43 | Come over to [Gitter](https://gitter.im/klaussinani/tusk) or [Twitter](https://twitter.com/klaussinani) to share your thoughts on the project. 44 | 45 | Visit the [contributing guidelines](https://github.com/klaussinani/tusk/blob/master/contributing.md#translating-documentation) to learn more on how to translate this document into more languages. 46 | 47 | You can find more apps [here](#related-apps). 48 | 49 | ## Highlights 50 | 51 | - Black, Dark & Sepia Themes 52 | - Focus, Compact & Auto-Night Modes 53 | - Local & Global Customizable Keyboard Shortcuts 54 | - Export Notes as PDF, HTML & Markdown Files 55 | - Note Navigation 56 | - Yinxiang Support 57 | - Cross Platform 58 | - Scalable Interface 59 | - Update Notifications 60 | - Drag and Drop Files 61 | 62 | ## Contents 63 | 64 | - [Description](#description) 65 | - [Highlights](#highlights) 66 | - [Install](#install) 67 | - [Features](#features) 68 | - [Keyboard Shortcuts](#keyboard-shortcuts) 69 | - [Development](#development) 70 | - [Related Apps](#related-apps) 71 | - [Team](#team) 72 | - [Sponsors](#sponsors) 73 | - [Disclaimer](#disclaimer) 74 | - [License](#license) 75 | 76 | ## Install 77 | 78 | #### Github Releases 79 | 80 | Head to the [releases](https://github.com/klaussinani/tusk/releases/latest) page and download the appropriate installer for your system. 81 | 82 | #### Snapcraft 83 | 84 | Ubuntu Linux users can directly install through [Snapcraft](https://snapcraft.io/tusk) `snap install tusk` 85 | 86 | #### Homebrew 87 | 88 | Macos users can directly install through [Homebrew Cask](https://caskroom.github.io/) `brew cask install tusk` 89 | 90 | #### Note 91 | 92 | The version available on `Homebrew Cask` may not be the latest, since unlike `Snapcraft`, it is not offically maintained. If that is the case, please consider downloading directly from the [Github releases](https://github.com/klaussinani/tusk/releases/latest) page. 93 | 94 | ## Features 95 | 96 | Visit the project [homepage](https://klaussinani.github.io/tusk) to view all features in detail. 97 | 98 | - Auto Night Mode - Press Cmd/Ctrl Alt N to allow Tusk to adjust to your environment. 99 | - Black Theme - Activate it by pressing Cmd/Ctrl Alt E. 100 | - Compact Mode - Downsize the window to enter the mode. 101 | - Custom Shortcut Keys - Navigate to `~/.tusk.json` or press Cmd/Ctrl . to modify any shortcut key. To reset delete `~/.tusk.json` & restart the app. 102 | - Dark Theme - Activate it by pressing Cmd/Ctrl D. 103 | - Drag & Drop Files - Attach files by dragging them to the app window. 104 | - Export Notes as Markdown - Press Cmd/Ctrl O to save your notes as `Markdown` files. 105 | - Export Notes as HTML - Press Cmd/Ctrl Shift H to save your notes as `HTML` files. 106 | - Export Notes as PDF - Press Cmd/Ctrl Shift E to save your notes as `PDF` files. 107 | - Focus Mode - Activate it by pressing Cmd/Ctrl K. 108 | - Global Shortcut Keys - Enable them by using the `File` > `Enable Global Shortcut Keys` option. 109 | - Note Navigation - Navigate your notes by pressing Cmd/Ctrl Tab / Cmd/Ctrl Shift Tab or jump directly to one by using Cmd/Ctrl 1 - 9. 110 | - Note Printing - Press Cmd/Ctrl Alt P to print your notes. 111 | - Scalable Interface - Adjust the zooming factor by pressing Cmd/Ctrl Shift = or Cmd/Ctrl -. 112 | - Sepia Theme - Activate it by pressing Cmd/Ctrl G. 113 | - Update Notifications - Customize the apps update checking frequency. 114 | - Yinxiang Support - Login to Yinxiang by using the `File` > `Switch to Yinxiang` option. 115 | 116 | ## Keyboard Shortcuts 117 | 118 | ### Local Shortcut Keys 119 | 120 | 70+ local keyboard shortcuts. Toggle anything in a flash. 121 | 122 |
123 | View all the available local keyboard shortcuts. 124 | 125 |
126 | 127 | Description | Keys 128 | -------------------------- | -------------------------- 129 | Activate Auto Night Mode | Cmd/Ctrl Alt N 130 | Add Link | Cmd/Ctrl Shift K 131 | Add Shortcut | Cmd/Ctrl Alt S 132 | Align Center | Cmd/Ctrl Alt M 133 | Align Left | Cmd/Ctrl Alt L 134 | Align Right | Cmd/Ctrl Alt R 135 | Attach File | Cmd/Ctrl Shift F 136 | Bold Text | Cmd/Ctrl B 137 | Bulleted List | Cmd/Ctrl Shift . 138 | Change Font Size | Cmd/Ctrl Alt 1 - 6 139 | Code Block | Cmd/Ctrl Shift L 140 | Decrease Indentation | Cmd/Ctrl Shift M 141 | Delete Note | Delete 142 | Edit Shortcut Keys | Cmd/Ctrl . 143 | Export Note as HTML | Cmd/Ctrl Shift H 144 | Export Note as Markdown | Cmd/Ctrl O 145 | Export Note as PDF | Cmd/Ctrl Shift E 146 | Increase Indentation | Cmd/Ctrl Alt K 147 | Insert Date Stamp | Cmd/Ctrl Shift ; 148 | Insert Date-Time Stamp | Cmd/Ctrl ; 149 | Insert from Drive | Cmd/Ctrl Shift D 150 | Insert Horizontal Rule | Cmd/Ctrl Shift - 151 | Italic Text | Cmd/Ctrl I 152 | Jump to Note | Cmd/Ctrl 1 - 9 153 | Make Text Larger | Cmd/Ctrl Shift = 154 | Make Text Smaller | Cmd/Ctrl - 155 | Navigate to Next Note | Cmd/Ctrl Tab 156 | Navigate to Previews Note | Cmd/Ctrl Shift Tab 157 | New Note | Cmd/Ctrl N 158 | New Notebook | Cmd/Ctrl Shift N 159 | New Tag | Cmd/Ctrl Shift T 160 | Numbered List | Cmd/Ctrl Shift O 161 | Print Note | Cmd/Ctrl Alt P 162 | Remove Formatting | Cmd/Ctrl Shift Space 163 | Reset Zoom Level | Cmd/Ctrl 0 164 | Return to Notes | Esc 165 | Save Note | Cmd/Ctrl S 166 | Search Notes | Cmd/Ctrl F 167 | Set Always on Top | Cmd/Ctrl Shift P 168 | Set Reminder | Cmd/Ctrl E 169 | Strikethrough Text | Cmd/Ctrl T 170 | Subscript Text | Cmd/Ctrl Shift ] 171 | Superscript Text | Cmd/Ctrl Shift [ 172 | Toggle Black Theme | Cmd/Ctrl Alt E 173 | Toggle Checkbox | Cmd/Ctrl Shift B 174 | Toggle Dark Theme | Cmd/Ctrl D 175 | Toggle Focus Mode | Cmd/Ctrl K 176 | Toggle Notebooks | Alt Shift N 177 | Toggle Sepia Theme | Cmd/Ctrl G 178 | Toggle Settings | Cmd/Ctrl , 179 | Toggle Shortcuts | Cmd/Ctrl Shift S 180 | Toggle Sidebar | Cmd/Ctrl \\ 181 | Toggle Tags | Alt Shift T 182 | Toggle Window Menu | Alt 183 | Underline Text | Cmd/Ctrl U 184 | 185 |
186 | 187 |
188 | 189 | ### Global Shortcut Keys 190 | 191 | Access Tusk at any moment from anywhere within your operating system. All global shortcuts can be customized to match your own preference through the configuration file `~/.tusk.json`. 192 | 193 |
194 | View all the available global keyboard shortcuts. 195 | 196 |
197 | 198 | Description | Global Shortcut 199 | -------------------------- | -------------------------- 200 | Toggle Tusk Window | Cmd/Ctrl Alt A 201 | Create New Note | Cmd/Ctrl Alt C 202 | Search Notes | Cmd/Ctrl Alt F 203 | 204 |
205 | 206 |
207 | 208 | ## Development 209 | 210 | For more info on how to contribute to the project, please read the [contributing guidelines](https://github.com/klaussinani/tusk/blob/master/contributing.md). 211 | 212 | - Fork the repository and clone it to your machine 213 | - Navigate to your local fork: `cd tusk` 214 | - Install the project dependencies: `npm install` or `yarn install` 215 | - Run Tusk on dev mode: `npm start` or `yarn start` 216 | - Lint code for errors: `npm test` or `yarn test` 217 | - Build binaries and installers: `npm run release` or `yarn release` 218 | 219 | ## Related Apps 220 | 221 | - [Ao](https://github.com/klaudiosinani/ao) - Elegant Microsoft To-Do desktop app. 222 | - [Taskbook](https://github.com/klaudiosinani/taskbook) - Tasks, boards & notes for the command-line habitat. 223 | 224 | ## Team 225 | 226 | - Klaudio Sinani [(@klaussinani)](https://github.com/klaudiosinani) 227 | - Mario Sinani [(@mariosinani)](https://github.com/mariosinani) 228 | - Athan Gkanos [(@athangkanos)](https://github.com/athangkanos) 229 | 230 | ## Sponsors 231 | 232 | A big thank you to all the people and companies supporting our Open Source work: 233 | 234 | - [Better Stack: Spot, Resolve, and Prevent Downtime.](https://betterstack.com/) 235 | 236 | ## Disclaimer 237 | 238 | Tusk is an unofficial, open source, third-party, community-driven, free app and is not affiliated in any way with Evernote. 239 | 240 | ## License 241 | 242 | [MIT](https://github.com/klaussinani/tusk/blob/master/license.md) 243 | -------------------------------------------------------------------------------- /src/browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {ipcRenderer: ipc} = require('electron'); 3 | const mode = require('./mode'); 4 | const nav = require('./nav'); 5 | const save = require('./save'); 6 | const settings = require('./settings'); 7 | const startup = require('./startup'); 8 | 9 | ipc.on('print', () => ipc.send('print-to-pdf')); 10 | 11 | ipc.on('export', () => ipc.send('export-as-pdf')); 12 | 13 | ipc.on('export-as-markdown', () => save.md()); 14 | 15 | ipc.on('export-as-html', () => save.html()); 16 | 17 | ipc.on('auto-launch', () => startup.autoLaunch()); 18 | 19 | ipc.on('next-note', () => nav.nextNote()); 20 | 21 | ipc.on('previous-note', () => nav.previousNote()); 22 | 23 | ipc.on('focus-mode', () => mode.toggleFocus()); 24 | 25 | ipc.on('toggle-dark-mode', () => mode.dark()); 26 | 27 | ipc.on('toggle-black-mode', () => mode.black()); 28 | 29 | ipc.on('toggle-sepia-mode', () => mode.sepia()); 30 | 31 | ipc.on('auto-night-mode', () => mode.autoNight()); 32 | 33 | ipc.on('toggle-side-bar', () => { 34 | settings.set('sideBarHidden', !settings.get('sideBarHidden')); 35 | nav.sideBar(); 36 | }); 37 | 38 | ipc.on('zoom-in', () => nav.zoomIn()); 39 | 40 | ipc.on('zoom-out', () => nav.zoomOut()); 41 | 42 | ipc.on('zoom-reset', () => nav.zoomReset()); 43 | 44 | ipc.on('log-out', () => { 45 | nav.click('#gwt-debug-AccountMenu-avatar'); 46 | nav.click('#gwt-debug-AccountMenu-logout'); 47 | }); 48 | 49 | ipc.on('new-notebook', () => { 50 | const notebooks = nav.select('#gwt-debug-Sidebar-notebooksButton'); 51 | notebooks.click(); 52 | nav.click('#gwt-debug-NotebooksDrawer-createNotebookButton'); 53 | notebooks.click(); 54 | }); 55 | 56 | ipc.on('new-tag', () => { 57 | const tags = nav.select('#gwt-debug-Sidebar-tagsButton'); 58 | tags.click(); 59 | nav.click('.focus-drawer-TagsDrawer-TagsDrawer-create-tag-icon'); 60 | tags.click(); 61 | }); 62 | 63 | ipc.on('subscript', () => { 64 | const formatMenu = nav.select('#gwt-debug-FormattingBar-overflowButton'); 65 | formatMenu.click(); 66 | nav.click('#gwt-debug-FormattingBar-subscriptButton'); 67 | }); 68 | 69 | ipc.on('superscript', () => { 70 | const formatMenu = nav.select('#gwt-debug-FormattingBar-overflowButton'); 71 | formatMenu.click(); 72 | nav.click('#gwt-debug-FormattingBar-superscriptButton'); 73 | }); 74 | 75 | ipc.on('add-link', () => { 76 | nav.click('#gwt-debug-FormattingBar-linkButton'); 77 | }); 78 | 79 | ipc.on('add-shortcut', () => { 80 | nav.click('#gwt-debug-NoteAttributes-shortcutButton'); 81 | }); 82 | 83 | ipc.on('align-center', () => { 84 | nav.click('#gwt-debug-EditorAlignDropdown-center'); 85 | }); 86 | 87 | ipc.on('align-left', () => { 88 | nav.click('#gwt-debug-EditorAlignDropdown-left'); 89 | }); 90 | 91 | ipc.on('align-right', () => { 92 | nav.click('#gwt-debug-EditorAlignDropdown-right'); 93 | }); 94 | 95 | ipc.on('attach-file', () => { 96 | nav.click('#gwt-debug-FormattingBar-linkButton'); 97 | }); 98 | 99 | ipc.on('bold', () => { 100 | nav.click('#gwt-debug-FormattingBar-boldButton'); 101 | }); 102 | 103 | ipc.on('bulleted', () => { 104 | nav.click('#gwt-debug-FormattingBar-bulletButton'); 105 | }); 106 | 107 | ipc.on('checkbox', () => { 108 | nav.click('#gwt-debug-FormattingBar-checkboxButton'); 109 | }); 110 | 111 | ipc.on('code-block', () => { 112 | nav.click('#gwt-debug-FormattingBar-codeBlockButton'); 113 | }); 114 | 115 | ipc.on('delete-note', () => { 116 | nav.click('#gwt-debug-NoteAttributes-trashButton'); 117 | }); 118 | 119 | ipc.on('header-five', () => { 120 | nav.click('#gwt-debug-FontSizeDropdown-root-TWELVE'); 121 | }); 122 | 123 | ipc.on('header-four', () => { 124 | nav.click('#gwt-debug-FontSizeDropdown-root-FOURTEEN'); 125 | }); 126 | 127 | ipc.on('header-one', () => { 128 | nav.click('#gwt-debug-FontSizeDropdown-root-THIRTY_SIX'); 129 | }); 130 | 131 | ipc.on('header-six', () => { 132 | nav.click('#gwt-debug-FontSizeDropdown-root-TEN'); 133 | }); 134 | 135 | ipc.on('header-three', () => { 136 | nav.click('#gwt-debug-FontSizeDropdown-root-EIGHTEEN'); 137 | }); 138 | 139 | ipc.on('header-two', () => { 140 | nav.click('#gwt-debug-FontSizeDropdown-root-TWENTY_FOUR'); 141 | }); 142 | 143 | ipc.on('horizontal-rule', () => { 144 | nav.click('#gwt-debug-FormattingBar-horizontalRuleButton'); 145 | }); 146 | 147 | ipc.on('indent', () => { 148 | nav.click('#gwt-debug-FormattingBar-indentButton'); 149 | }); 150 | 151 | ipc.on('insert-drive', () => { 152 | nav.click('#gwt-debug-FormattingBar-attachmentButton'); 153 | }); 154 | 155 | ipc.on('italic', () => { 156 | nav.click('#gwt-debug-FormattingBar-italicButton'); 157 | }); 158 | 159 | ipc.on('new-note', () => { 160 | nav.click('#gwt-debug-Sidebar-newNoteButton'); 161 | }); 162 | 163 | ipc.on('numbered', () => { 164 | nav.click('#gwt-debug-FormattingBar-listButton'); 165 | }); 166 | 167 | ipc.on('outdent', () => { 168 | nav.click('#gwt-debug-FormattingBar-outdentButton'); 169 | }); 170 | 171 | ipc.on('remove-formatting', () => { 172 | nav.click('#gwt-debug-FormattingBar-noFormatButton'); 173 | }); 174 | 175 | ipc.on('return', () => { 176 | nav.click('#gwt-debug-Sidebar-notesButton'); 177 | }); 178 | 179 | ipc.on('search', () => { 180 | nav.click('#gwt-debug-Sidebar-searchButton'); 181 | }); 182 | 183 | ipc.on('set-reminder', () => { 184 | nav.click('#gwt-debug-NoteAttributes-reminderButton'); 185 | }); 186 | 187 | ipc.on('shortcuts', () => { 188 | nav.click('#gwt-debug-Sidebar-shortcutsButton'); 189 | }); 190 | 191 | ipc.on('strikethrough', () => { 192 | nav.click('#gwt-debug-FormattingBar-strikeButton'); 193 | }); 194 | 195 | ipc.on('toggle-notebooks', () => { 196 | nav.click('#gwt-debug-Sidebar-notebooksButton'); 197 | }); 198 | 199 | ipc.on('toggle-tags', () => { 200 | nav.click('#gwt-debug-Sidebar-tagsButton'); 201 | }); 202 | 203 | ipc.on('underline', () => { 204 | nav.click('#gwt-debug-FormattingBar-underlineButton'); 205 | }); 206 | 207 | document.addEventListener('keydown', e => nav.jumpToNote(e)); 208 | 209 | document.addEventListener('DOMContentLoaded', () => { 210 | nav.zoomRestore(); 211 | 212 | if (settings.get('autoNightMode')) { 213 | mode.autoNight(); 214 | } 215 | 216 | nav.sideBar(); 217 | mode.restore(); 218 | mode.autoWritingDirection(); 219 | }); 220 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const defaultConfig = require('./configs'); 4 | const file = require('./file'); 5 | 6 | const {log} = console; 7 | 8 | class Config { 9 | constructor() { 10 | this._default = Object.assign({}, defaultConfig); 11 | } 12 | 13 | get _local() { 14 | try { 15 | return JSON.parse(fs.readFileSync(file.localConfig, 'utf8')); 16 | } catch (error) { 17 | return log(error); 18 | } 19 | } 20 | 21 | get configuration() { 22 | this._ensureLocalConfig(file.localConfig); 23 | return this._local; 24 | } 25 | 26 | get shortcutKeys() { 27 | return this.configuration.shortcutKeys; 28 | } 29 | 30 | _updateConfig(data) { 31 | const result = Object.assign({}, this._default); 32 | 33 | Object.keys(data).forEach(type => { 34 | result[type] = Object.assign({}, result[type], data[type]); 35 | }); 36 | 37 | Object.keys(result).forEach(type => { 38 | const [opts, defaultOpts] = [data[type], this._default[type]].map(Object.keys); 39 | const deprecated = opts.filter(x => !defaultOpts.includes(x)); 40 | deprecated.forEach(x => delete result[type][x]); 41 | }); 42 | 43 | return result; 44 | } 45 | 46 | _ensureLocalConfig(path) { 47 | const data = fs.existsSync(path) ? this._updateConfig(this._local) : this._default; 48 | try { 49 | fs.writeFileSync(path, JSON.stringify(data, null, 4)); 50 | } catch (error) { 51 | log(error); 52 | } 53 | } 54 | } 55 | 56 | module.exports = new Config(); 57 | -------------------------------------------------------------------------------- /src/configs/darwin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | shortcutKeys: { 5 | 'add-link': 'Cmd+Shift+K', 6 | 'add-shortcut': 'Cmd+Alt+S', 7 | 'align-center': 'Cmd+Alt+M', 8 | 'align-left': 'Cmd+Alt+L', 9 | 'align-right': 'Cmd+Alt+R', 10 | 'attach-file': 'Cmd+Shift+F', 11 | 'bold': 'Cmd+B', 12 | 'bulleted': 'Cmd+Shift+.', 13 | 'checkbox': 'Cmd+Shift+B', 14 | 'code-block': 'Cmd+Shift+L', 15 | 'date-stamp': 'Cmd+Shift+;', 16 | 'date-time-stamp': 'Cmd+;', 17 | 'decrease-indentation': 'Cmd+Shift+M', 18 | 'delete-note': 'Delete', 19 | 'export-html': 'Cmd+Shift+H', 20 | 'export-markdown': 'Cmd+O', 21 | 'export-pdf': 'Cmd+Shift+E', 22 | 'global-create-note': 'Cmd+Alt+C', 23 | 'global-search-note': 'Cmd+Alt+F', 24 | 'global-toggle-window': 'Cmd+Alt+A', 25 | 'horizontal-rule': 'Cmd+Shift+-', 26 | 'increase-indentation': 'Cmd+Alt+K', 27 | 'insert-drive': 'Cmd+Shift+D', 28 | 'italic': 'Cmd+I', 29 | 'new-note': 'Cmd+N', 30 | 'new-notebook': 'Cmd+Shift+N', 31 | 'new-tag': 'Cmd+Shift+T', 32 | 'numbered': 'Cmd+Shift+O', 33 | 'print': 'Cmd+Alt+P', 34 | 'remove-formatting': 'Cmd+Shift+Space', 35 | 'return': 'Esc', 36 | 'search': 'Cmd+F', 37 | 'set-reminder': 'Cmd+E', 38 | 'settings': 'Cmd+,', 39 | 'shortcuts': 'Cmd+Shift+S', 40 | 'strikethrough': 'Cmd+T', 41 | 'subscript': 'Cmd+Shift+]', 42 | 'superscript': 'Cmd+Shift+[', 43 | 'toggle-black-mode': 'Cmd+Alt+E', 44 | 'toggle-dark-mode': 'Cmd+D', 45 | 'toggle-focus-mode': 'Cmd+K', 46 | 'toggle-notebooks': 'Shift+Alt+N', 47 | 'toggle-sepia-mode': 'Cmd+G', 48 | 'toggle-sidebar': 'Cmd+\\', 49 | 'toggle-tags': 'Shift+Alt+T', 50 | 'underline': 'Cmd+U' 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/configs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const config = require(`./${process.platform}`); 3 | 4 | module.exports = config; 5 | -------------------------------------------------------------------------------- /src/configs/linux.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | shortcutKeys: { 5 | 'add-link': 'Ctrl+Shift+K', 6 | 'add-shortcut': 'Ctrl+Alt+S', 7 | 'align-center': 'Ctrl+Alt+M', 8 | 'align-left': 'Ctrl+Alt+L', 9 | 'align-right': 'Ctrl+Alt+R', 10 | 'attach-file': 'Ctrl+Shift+F', 11 | 'bold': 'Ctrl+B', 12 | 'bulleted': 'Ctrl+Shift+.', 13 | 'checkbox': 'Ctrl+Shift+B', 14 | 'code-block': 'Ctrl+Shift+L', 15 | 'date-stamp': 'Ctrl+Shift+;', 16 | 'date-time-stamp': 'Ctrl+;', 17 | 'decrease-indentation': 'Ctrl+Shift+M', 18 | 'delete-note': 'Delete', 19 | 'export-html': 'Ctrl+Shift+H', 20 | 'export-markdown': 'Ctrl+O', 21 | 'export-pdf': 'Ctrl+Shift+E', 22 | 'global-create-note': 'Ctrl+Alt+C', 23 | 'global-search-note': 'Ctrl+Alt+F', 24 | 'global-toggle-window': 'Ctrl+Alt+A', 25 | 'horizontal-rule': 'Ctrl+Shift+-', 26 | 'increase-indentation': 'Ctrl+Alt+K', 27 | 'insert-drive': 'Ctrl+Shift+D', 28 | 'italic': 'Ctrl+I', 29 | 'new-note': 'Ctrl+N', 30 | 'new-notebook': 'Ctrl+Shift+N', 31 | 'new-tag': 'Ctrl+Shift+T', 32 | 'numbered': 'Ctrl+Shift+O', 33 | 'print': 'Ctrl+Alt+P', 34 | 'remove-formatting': 'Ctrl+Shift+Space', 35 | 'return': 'Esc', 36 | 'search': 'Ctrl+F', 37 | 'set-reminder': 'Ctrl+E', 38 | 'settings': 'Ctrl+,', 39 | 'shortcuts': 'Ctrl+Shift+S', 40 | 'strikethrough': 'Ctrl+T', 41 | 'subscript': 'Ctrl+Shift+]', 42 | 'superscript': 'Ctrl+Shift+[', 43 | 'toggle-black-mode': 'Ctrl+Alt+E', 44 | 'toggle-dark-mode': 'Ctrl+D', 45 | 'toggle-focus-mode': 'Ctrl+K', 46 | 'toggle-notebooks': 'Shift+Alt+N', 47 | 'toggle-sepia-mode': 'Ctrl+G', 48 | 'toggle-sidebar': 'Ctrl+\\', 49 | 'toggle-tags': 'Shift+Alt+T', 50 | 'underline': 'Ctrl+U' 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/configs/win32.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | shortcutKeys: { 5 | 'add-link': 'Ctrl+Shift+K', 6 | 'add-shortcut': 'Ctrl+Alt+S', 7 | 'align-center': 'Ctrl+Alt+M', 8 | 'align-left': 'Ctrl+Alt+L', 9 | 'align-right': 'Ctrl+Alt+R', 10 | 'attach-file': 'Ctrl+Shift+F', 11 | 'bold': 'Ctrl+B', 12 | 'bulleted': 'Ctrl+Shift+.', 13 | 'checkbox': 'Ctrl+Shift+B', 14 | 'code-block': 'Ctrl+Shift+L', 15 | 'date-stamp': 'Ctrl+Shift+;', 16 | 'date-time-stamp': 'Ctrl+;', 17 | 'decrease-indentation': 'Ctrl+Shift+M', 18 | 'delete-note': 'Delete', 19 | 'export-html': 'Ctrl+Shift+H', 20 | 'export-markdown': 'Ctrl+O', 21 | 'export-pdf': 'Ctrl+Shift+E', 22 | 'global-create-note': 'Ctrl+Alt+C', 23 | 'global-search-note': 'Ctrl+Alt+F', 24 | 'global-toggle-window': 'Ctrl+Alt+A', 25 | 'horizontal-rule': 'Ctrl+Shift+-', 26 | 'increase-indentation': 'Ctrl+Alt+K', 27 | 'insert-drive': 'Ctrl+Shift+D', 28 | 'italic': 'Ctrl+I', 29 | 'new-note': 'Ctrl+N', 30 | 'new-notebook': 'Ctrl+Shift+N', 31 | 'new-tag': 'Ctrl+Shift+T', 32 | 'numbered': 'Ctrl+Shift+O', 33 | 'print': 'Ctrl+Alt+P', 34 | 'remove-formatting': 'Ctrl+Shift+Space', 35 | 'return': 'Esc', 36 | 'search': 'Ctrl+F', 37 | 'set-reminder': 'Ctrl+E', 38 | 'settings': 'Ctrl+,', 39 | 'shortcuts': 'Ctrl+Shift+S', 40 | 'strikethrough': 'Ctrl+T', 41 | 'subscript': 'Ctrl+Shift+]', 42 | 'superscript': 'Ctrl+Shift+[', 43 | 'toggle-black-mode': 'Ctrl+Alt+E', 44 | 'toggle-dark-mode': 'Ctrl+D', 45 | 'toggle-focus-mode': 'Ctrl+K', 46 | 'toggle-notebooks': 'Shift+Alt+N', 47 | 'toggle-sepia-mode': 'Ctrl+G', 48 | 'toggle-sidebar': 'Ctrl+\\', 49 | 'toggle-tags': 'Shift+Alt+T', 50 | 'underline': 'Ctrl+U' 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/dialog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {app, clipboard, dialog, shell} = require('electron'); 3 | const os = require('os'); 4 | const {activate} = require('./win'); 5 | const {release} = require('./url'); 6 | const file = require('./file'); 7 | const settings = require('./settings'); 8 | 9 | class Dialog { 10 | get _systemInfo() { 11 | return [ 12 | `Version: ${app.getVersion()}`, 13 | `Electron: ${process.versions.electron}`, 14 | `Chrome: ${process.versions.chrome}`, 15 | `Node: ${process.versions.node}`, 16 | `V8: ${process.versions.v8}`, 17 | `OS: ${os.type()} ${os.arch()} ${os.release()}` 18 | ].join('\n'); 19 | } 20 | 21 | _about() { 22 | return this._create({ 23 | buttons: ['Done', 'Copy'], 24 | detail: `Created by Klaus Sinani.\n\n${this._systemInfo}`, 25 | message: `Tusk ${app.getVersion()} (${os.arch()})`, 26 | title: 'About Tusk' 27 | }); 28 | } 29 | 30 | _create(options) { 31 | return dialog.showMessageBox( 32 | Object.assign({ 33 | cancelId: 1, 34 | defaultId: 0, 35 | icon: file.icon 36 | }, options) 37 | ); 38 | } 39 | 40 | _exit() { 41 | return this._create({ 42 | buttons: ['Exit', 'Dismiss'], 43 | detail: 'Are you sure you want to exit?', 44 | message: 'Exit Tusk', 45 | title: 'Tusk - Exit Confirmation' 46 | }); 47 | } 48 | 49 | _signOut() { 50 | return this._create({ 51 | buttons: ['Sign Out', 'Dismiss'], 52 | detail: 'Are you sure you want to sign out?', 53 | message: 'Sign out of Tusk', 54 | title: 'Tusk - Sign Out Confirmation' 55 | }); 56 | } 57 | 58 | _restart() { 59 | return this._create({ 60 | buttons: ['Restart', 'Dismiss'], 61 | detail: 'Would you like to restart now?', 62 | message: 'Restart Tusk to activate your new settings', 63 | title: 'Tusk - Restart Required' 64 | }); 65 | } 66 | 67 | _update(version) { 68 | return this._create({ 69 | buttons: ['Download', 'Dismiss'], 70 | detail: 'Click Download to get it now', 71 | message: `Version ${version} is now available`, 72 | title: 'Update Tusk' 73 | }); 74 | } 75 | 76 | confirmAbout() { 77 | if (this._about() === 1) { 78 | clipboard.writeText(this._systemInfo); 79 | } 80 | } 81 | 82 | confirmExit() { 83 | if (settings.get('requestExitConfirmation')) { 84 | if (this._exit() === 0) { 85 | app.quit(); 86 | } 87 | } else { 88 | app.quit(); 89 | } 90 | } 91 | 92 | confirmActivationRestart(option, state) { 93 | if (this._restart() === 0) { 94 | settings.set(option, state); 95 | app.quit(); 96 | app.relaunch(); 97 | } 98 | } 99 | 100 | confirmSignOut() { 101 | if (this._signOut() === 0) { 102 | activate('log-out'); 103 | } 104 | } 105 | 106 | updateError(content) { 107 | return dialog.showErrorBox('Request to get update failed', content); 108 | } 109 | 110 | noUpdate() { 111 | return this._create({ 112 | detail: `Tusk is running on the latest ${app.getVersion()} version`, 113 | message: 'There are currently no updates available', 114 | title: 'Tusk - No Update Available', 115 | buttons: ['Done'] 116 | }); 117 | } 118 | 119 | getUpdate(version) { 120 | if (this._update(version) === 0) { 121 | shell.openExternal(release); 122 | } 123 | } 124 | } 125 | 126 | module.exports = new Dialog(); 127 | -------------------------------------------------------------------------------- /src/file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {join} = require('path'); 3 | const {homedir} = require('os'); 4 | 5 | module.exports = { 6 | icon: join(__dirname, '../static/Icon.png'), 7 | localConfig: join(homedir(), '.tusk.json'), 8 | style: join(__dirname, './style'), 9 | trayIcon: join(__dirname, '../static/IconTray.png') 10 | }; 11 | -------------------------------------------------------------------------------- /src/keymap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {globalShortcut} = require('electron'); 3 | const {shortcutKeys} = require('./config'); 4 | const win = require('./win'); 5 | 6 | const {log} = console; 7 | 8 | class Keymap { 9 | setAcc(custom, predefined) { 10 | if (Object.prototype.hasOwnProperty.call(shortcutKeys, custom)) { 11 | return shortcutKeys[custom]; 12 | } 13 | 14 | return predefined; 15 | } 16 | 17 | registerGlobal() { 18 | const toggleTusk = globalShortcut.register( 19 | this.setAcc('global-toggle-window', 'CmdorCtrl+Alt+A'), () => { 20 | win.toggle(); 21 | }); 22 | 23 | const searchNote = globalShortcut.register( 24 | this.setAcc('global-search-note', 'CmdorCtrl+Alt+F'), () => { 25 | win.appear(); 26 | win.activate('search'); 27 | }); 28 | 29 | const createNote = globalShortcut.register( 30 | this.setAcc('global-create-note', 'CmdorCtrl+Alt+C'), () => { 31 | win.appear(); 32 | win.activate('new-note'); 33 | }); 34 | 35 | if (toggleTusk && searchNote && createNote) { 36 | log('Successfully registered global shortcut keys'); 37 | } else { 38 | log('Global shortcut keys registration failed'); 39 | } 40 | } 41 | } 42 | 43 | module.exports = new Keymap(); 44 | -------------------------------------------------------------------------------- /src/menu/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {app} = require('electron'); 3 | const dialog = require('./../dialog'); 4 | 5 | module.exports = { 6 | label: app.getName(), 7 | submenu: [ 8 | { 9 | role: 'services', 10 | submenu: [] 11 | }, { 12 | type: 'separator' 13 | }, { 14 | role: 'hide' 15 | }, { 16 | role: 'hideothers' 17 | }, { 18 | role: 'unhide' 19 | }, { 20 | type: 'separator' 21 | }, { 22 | label: 'Exit', 23 | click() { 24 | dialog.confirmExit(); 25 | } 26 | } 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /src/menu/edit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | label: 'Edit', 5 | submenu: [ 6 | { 7 | type: 'separator' 8 | }, { 9 | role: 'undo' 10 | }, { 11 | role: 'redo' 12 | }, { 13 | type: 'separator' 14 | }, { 15 | role: 'cut' 16 | }, { 17 | role: 'copy' 18 | }, { 19 | role: 'paste' 20 | }, { 21 | role: 'pasteandmatchstyle' 22 | }, { 23 | role: 'delete' 24 | }, { 25 | role: 'selectall' 26 | } 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /src/menu/file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {app, shell} = require('electron'); 3 | const {activate} = require('./../win'); 4 | const {is} = require('./../util'); 5 | const {setAcc} = require('./../keymap'); 6 | const dialog = require('./../dialog'); 7 | const file = require('./../file'); 8 | const settings = require('./../settings'); 9 | const url = require('./../url'); 10 | 11 | module.exports = { 12 | label: 'File', 13 | submenu: [ 14 | { 15 | label: 'Search', 16 | accelerator: setAcc('search', 'CmdorCtrl+F'), 17 | click() { 18 | activate('search'); 19 | } 20 | }, { 21 | type: 'separator' 22 | }, { 23 | label: 'Create', 24 | submenu: [ 25 | { 26 | label: 'New Note', 27 | accelerator: setAcc('new-note', 'CmdorCtrl+N'), 28 | click() { 29 | activate('new-note'); 30 | } 31 | }, { 32 | label: 'Delete Note', 33 | accelerator: setAcc('delete-note', 'Delete'), 34 | click() { 35 | activate('delete-note'); 36 | } 37 | }, { 38 | type: 'separator' 39 | }, { 40 | label: 'New Tag', 41 | accelerator: setAcc('new-tag', 'CmdorCtrl+Shift+T'), 42 | click() { 43 | activate('new-tag'); 44 | } 45 | }, { 46 | label: 'New Notebook', 47 | accelerator: setAcc('new-notebook', 'CmdorCtrl+Shift+N'), 48 | click() { 49 | activate('new-notebook'); 50 | } 51 | }, { 52 | type: 'separator' 53 | }, { 54 | label: 'Set Reminder', 55 | accelerator: setAcc('set-reminder', 'CmdorCtrl+E'), 56 | click() { 57 | activate('set-reminder'); 58 | } 59 | }, { 60 | label: 'Add Shortcut', 61 | accelerator: setAcc('add-shortcut', 'CmdorCtrl+Alt+S'), 62 | click() { 63 | activate('add-shortcut'); 64 | } 65 | } 66 | ] 67 | }, { 68 | type: 'separator' 69 | }, { 70 | label: 'Navigate', 71 | submenu: [ 72 | { 73 | label: 'Tags', 74 | accelerator: setAcc('toggle-tags', 'Shift+Alt+T'), 75 | click() { 76 | activate('toggle-tags'); 77 | } 78 | }, { 79 | label: 'Shortcuts', 80 | accelerator: setAcc('shortcuts', 'CmdorCtrl+Shift+S'), 81 | click() { 82 | activate('shortcuts'); 83 | } 84 | }, { 85 | label: 'Notebooks', 86 | accelerator: setAcc('toggle-notebooks', 'Shift+Alt+N'), 87 | click() { 88 | activate('toggle-notebooks'); 89 | } 90 | }, { 91 | type: 'separator' 92 | }, { 93 | label: 'Return to Notes', 94 | accelerator: setAcc('return', 'Esc'), 95 | click() { 96 | activate('return'); 97 | } 98 | } 99 | ] 100 | }, { 101 | type: 'separator' 102 | }, { 103 | label: 'Print Note', 104 | accelerator: setAcc('print', 'CmdorCtrl+Alt+P'), 105 | click() { 106 | activate('print'); 107 | } 108 | }, { 109 | label: 'Export Note as', 110 | submenu: [ 111 | { 112 | label: 'PDF File', 113 | accelerator: setAcc('export-pdf', 'CmdorCtrl+Shift+E'), 114 | click() { 115 | activate('export'); 116 | } 117 | }, { 118 | label: 'HTML File', 119 | accelerator: setAcc('export-html', 'CmdorCtrl+Shift+H'), 120 | click() { 121 | activate('export-as-html'); 122 | } 123 | }, { 124 | label: 'Markdown File', 125 | accelerator: setAcc('export-markdown', 'CmdorCtrl+O'), 126 | click() { 127 | activate('export-as-markdown'); 128 | } 129 | } 130 | ] 131 | }, { 132 | type: 'separator' 133 | }, { 134 | label: 'Evernote Settings', 135 | accelerator: setAcc('settings', 'CmdorCtrl+,'), 136 | click() { 137 | shell.openExternal(url.settings); 138 | } 139 | }, { 140 | label: 'Launch on Start', 141 | type: 'checkbox', 142 | checked: settings.get('autoLaunch'), 143 | click(item) { 144 | settings.set('autoLaunch', item.checked); 145 | activate('auto-launch'); 146 | } 147 | }, { 148 | label: 'Launch Minimized', 149 | type: 'checkbox', 150 | checked: settings.get('launchMinimized'), 151 | click(item) { 152 | settings.set('launchMinimized', item.checked); 153 | } 154 | }, { 155 | type: 'separator' 156 | }, { 157 | label: 'Edit Shortcut Keys', 158 | accelerator: 'CmdorCtrl+.', 159 | click() { 160 | shell.openExternal(file.localConfig); 161 | } 162 | }, { 163 | label: 'Enable Global Shortcut Keys', 164 | type: 'checkbox', 165 | checked: settings.get('useGlobalShortcuts'), 166 | click(item) { 167 | dialog.confirmActivationRestart('useGlobalShortcuts', item.checked); 168 | item.checked = settings.get('useGlobalShortcuts'); 169 | } 170 | }, { 171 | label: 'Request Exit Confirmation', 172 | type: 'checkbox', 173 | checked: settings.get('requestExitConfirmation'), 174 | click(item) { 175 | settings.set('requestExitConfirmation', item.checked); 176 | } 177 | }, { 178 | type: 'separator' 179 | }, { 180 | label: 'Switch to Yinxiang', 181 | visible: !settings.get('useYinxiang'), 182 | click() { 183 | settings.set('useYinxiang', true); 184 | app.relaunch(); 185 | app.quit(); 186 | } 187 | }, { 188 | label: 'Switch to Evernote', 189 | visible: settings.get('useYinxiang'), 190 | click() { 191 | settings.set('useYinxiang', false); 192 | app.relaunch(); 193 | app.quit(); 194 | } 195 | }, { 196 | type: 'separator' 197 | }, { 198 | label: 'Log Out', 199 | click() { 200 | dialog.confirmSignOut(); 201 | } 202 | }, { 203 | label: 'Exit', 204 | visible: !is.darwin, 205 | click() { 206 | dialog.confirmExit(); 207 | } 208 | } 209 | ] 210 | }; 211 | -------------------------------------------------------------------------------- /src/menu/format.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {BrowserWindow} = require('electron'); 3 | const {activate} = require('./../win'); 4 | const {setAcc} = require('./../keymap'); 5 | const time = require('./../time'); 6 | 7 | module.exports = { 8 | label: 'Format', 9 | submenu: [ 10 | { 11 | label: 'Style', 12 | submenu: [ 13 | { 14 | label: 'Bold text', 15 | accelerator: setAcc('bold', 'CmdorCtrl+B'), 16 | click() { 17 | activate('bold'); 18 | } 19 | }, { 20 | label: 'Italic text', 21 | accelerator: setAcc('italic', 'CmdorCtrl+I'), 22 | click() { 23 | activate('italic'); 24 | } 25 | }, { 26 | label: 'Underline text', 27 | accelerator: setAcc('underline', 'CmdorCtrl+U'), 28 | click() { 29 | activate('underline'); 30 | } 31 | }, { 32 | label: 'Strikethrough text', 33 | accelerator: setAcc('strikethrough', 'CmdorCtrl+T'), 34 | click() { 35 | activate('strikethrough'); 36 | } 37 | } 38 | ] 39 | }, { 40 | label: 'Font Size', 41 | submenu: [ 42 | { 43 | label: 'Header 1', 44 | accelerator: 'Alt+CmdorCtrl+1', 45 | click() { 46 | activate('header-one'); 47 | } 48 | }, { 49 | label: 'Header 2', 50 | accelerator: 'Alt+CmdorCtrl+2', 51 | click() { 52 | activate('header-two'); 53 | } 54 | }, { 55 | label: 'Header 3', 56 | accelerator: 'Alt+CmdorCtrl+3', 57 | click() { 58 | activate('header-three'); 59 | } 60 | }, { 61 | label: 'Header 4', 62 | accelerator: 'Alt+CmdorCtrl+4', 63 | click() { 64 | activate('header-four'); 65 | } 66 | }, { 67 | label: 'Header 5', 68 | accelerator: 'Alt+CmdorCtrl+5', 69 | click() { 70 | activate('header-five'); 71 | } 72 | }, { 73 | label: 'Header 6', 74 | accelerator: 'Alt+CmdorCtrl+6', 75 | click() { 76 | activate('header-six'); 77 | } 78 | } 79 | ] 80 | }, { 81 | label: 'Add link', 82 | accelerator: setAcc('add-link', 'CmdorCtrl+Shift+K'), 83 | click() { 84 | activate('add-link'); 85 | } 86 | }, { 87 | label: 'Attach file', 88 | accelerator: setAcc('attach-file', 'CmdorCtrl+Shift+F'), 89 | click() { 90 | activate('attach-file'); 91 | } 92 | }, { 93 | label: 'Insert from Drive', 94 | accelerator: setAcc('insert-drive', 'CmdorCtrl+Shift+D'), 95 | click() { 96 | activate('insert-drive'); 97 | } 98 | }, { 99 | label: 'Paragraph', 100 | submenu: [ 101 | { 102 | label: 'Align left', 103 | accelerator: setAcc('align-left', 'CmdorCtrl+Alt+L'), 104 | click() { 105 | activate('align-left'); 106 | } 107 | }, { 108 | label: 'Align center', 109 | accelerator: setAcc('align-center', 'CmdorCtrl+Alt+M'), 110 | click() { 111 | activate('align-center'); 112 | } 113 | }, { 114 | label: 'Align right', 115 | accelerator: setAcc('align-right', 'CmdorCtrl+Alt+R'), 116 | click() { 117 | activate('align-right'); 118 | } 119 | }, { 120 | type: 'separator' 121 | }, { 122 | label: 'Increase indentation', 123 | accelerator: setAcc('increase-indentation', 'CmdorCtrl+Alt+K'), 124 | click() { 125 | activate('increase-indentation'); 126 | } 127 | }, { 128 | label: 'Decrease indentation', 129 | accelerator: setAcc('decrease-indentation', 'CmdorCtrl+Shift+M'), 130 | click() { 131 | activate('decrease-indentation'); 132 | } 133 | }, { 134 | type: 'separator' 135 | }, { 136 | label: 'Numbered list', 137 | accelerator: setAcc('numbered', 'CmdorCtrl+Shift+O'), 138 | click() { 139 | activate('numbered'); 140 | } 141 | }, { 142 | label: 'Bulleted list', 143 | accelerator: setAcc('bulleted', 'CmdorCtrl+Shift+.'), 144 | click() { 145 | activate('bulleted'); 146 | } 147 | } 148 | ] 149 | }, { 150 | type: 'separator' 151 | }, { 152 | label: 'Insert Date Stamp', 153 | accelerator: setAcc('date-stamp', 'CmdOrCtrl+Shift+;'), 154 | click() { 155 | const [appWindow] = BrowserWindow.getAllWindows(); 156 | appWindow.webContents.insertText(time.date()); 157 | } 158 | }, { 159 | label: 'Insert Date-Time Stamp', 160 | accelerator: setAcc('date-time-stamp', 'CmdOrCtrl+;'), 161 | click() { 162 | const [appWindow] = BrowserWindow.getAllWindows(); 163 | appWindow.webContents.insertText(time.dateTime()); 164 | } 165 | }, { 166 | type: 'separator' 167 | }, { 168 | label: 'Checkbox', 169 | accelerator: setAcc('checkbox', 'CmdorCtrl+Shift+B'), 170 | click() { 171 | activate('checkbox'); 172 | } 173 | }, { 174 | label: 'Code block', 175 | accelerator: setAcc('code-block', 'CmdorCtrl+Shift+L'), 176 | click() { 177 | activate('code-block'); 178 | } 179 | }, { 180 | type: 'separator' 181 | }, { 182 | label: 'Subscript text', 183 | accelerator: setAcc('subscript', 'CmdorCtrl+Shift+]'), 184 | click() { 185 | activate('subscript'); 186 | } 187 | }, { 188 | label: 'Superscript text', 189 | accelerator: setAcc('superscript', 'CmdorCtrl+Shift+['), 190 | click() { 191 | activate('superscript'); 192 | } 193 | }, { 194 | type: 'separator' 195 | }, { 196 | label: 'Remove Formatting', 197 | accelerator: setAcc('remove-formatting', 'CmdorCtrl+Shift+Space'), 198 | click() { 199 | activate('remove-formatting'); 200 | } 201 | }, { 202 | label: 'Insert Horizontal Rule', 203 | accelerator: setAcc('horizontal-rule', 'CmdorCtrl+Shift+-'), 204 | click() { 205 | activate('horizontal-rule'); 206 | } 207 | } 208 | ] 209 | }; 210 | -------------------------------------------------------------------------------- /src/menu/help.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {app, shell} = require('electron'); 3 | const dialog = require('./../dialog'); 4 | const settings = require('./../settings'); 5 | const update = require('./../update'); 6 | const url = require('./../url'); 7 | 8 | module.exports = { 9 | label: 'Help', 10 | submenu: [ 11 | { 12 | label: 'View License', 13 | click() { 14 | shell.openExternal(url.license); 15 | } 16 | }, { 17 | label: `Version ${app.getVersion()}`, 18 | enabled: false 19 | }, { 20 | label: 'Tusk Homepage', 21 | click() { 22 | shell.openExternal(url.homepage); 23 | } 24 | }, { 25 | label: 'Check for Update', 26 | click() { 27 | update.check(); 28 | } 29 | }, { 30 | label: 'Update Check Frequency', 31 | enabled: !settings.get('disableAutoUpdateCheck'), 32 | submenu: [ 33 | { 34 | label: 'Once Every 4 Hours', 35 | type: 'checkbox', 36 | checked: settings.get('updateCheckPeriod') === '4', 37 | click(item) { 38 | dialog.confirmActivationRestart('updateCheckPeriod', '4'); 39 | item.checked = settings.get('updateCheckPeriod') === '4'; 40 | } 41 | }, { 42 | label: 'Once Every 8 Hours', 43 | type: 'checkbox', 44 | checked: settings.get('updateCheckPeriod') === '8', 45 | click(item) { 46 | dialog.confirmActivationRestart('updateCheckPeriod', '8'); 47 | item.checked = settings.get('updateCheckPeriod') === '8'; 48 | } 49 | }, { 50 | label: 'Once Every 12 Hours', 51 | type: 'checkbox', 52 | checked: settings.get('updateCheckPeriod') === '12', 53 | click(item) { 54 | dialog.confirmActivationRestart('updateCheckPeriod', '12'); 55 | item.checked = settings.get('updateCheckPeriod') === '12'; 56 | } 57 | }, { 58 | label: 'Once a Day', 59 | type: 'checkbox', 60 | checked: settings.get('updateCheckPeriod') === '24', 61 | click(item) { 62 | dialog.confirmActivationRestart('updateCheckPeriod', '24'); 63 | item.checked = settings.get('updateCheckPeriod') === '24'; 64 | } 65 | } 66 | ] 67 | }, { 68 | label: 'Disable Automatic Update Check', 69 | type: 'checkbox', 70 | checked: settings.get('disableAutoUpdateCheck'), 71 | click(item) { 72 | dialog.confirmActivationRestart('disableAutoUpdateCheck', item.checked); 73 | item.checked = settings.get('disableAutoUpdateCheck'); 74 | } 75 | }, { 76 | type: 'separator' 77 | }, { 78 | label: 'Keyboard Shortcuts Reference', 79 | click() { 80 | shell.openExternal(url.keyboardShortcutsRef); 81 | } 82 | }, { 83 | type: 'separator' 84 | }, { 85 | label: 'Fork Source', 86 | click() { 87 | shell.openExternal(url.source); 88 | } 89 | }, { 90 | label: 'Report Issue', 91 | click() { 92 | shell.openExternal(url.issue); 93 | } 94 | }, { 95 | label: 'Search Issues', 96 | click() { 97 | shell.openExternal(url.search); 98 | } 99 | }, { 100 | label: 'Search Feature Requests', 101 | click() { 102 | shell.openExternal(url.searchFeatureRequests); 103 | } 104 | }, { 105 | label: 'Community Discussion', 106 | click() { 107 | shell.openExternal(url.community); 108 | } 109 | }, { 110 | type: 'separator' 111 | }, { 112 | role: 'about', 113 | click() { 114 | dialog.confirmAbout(); 115 | } 116 | } 117 | ] 118 | }; 119 | -------------------------------------------------------------------------------- /src/menu/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {Menu} = require('electron'); 3 | const {is} = require('./../util'); 4 | const app = require('./app'); 5 | const edit = require('./edit'); 6 | const file = require('./file'); 7 | const format = require('./format'); 8 | const help = require('./help'); 9 | const view = require('./view'); 10 | const window = require('./window'); 11 | 12 | const darwin = [app, file, edit, format, view, window, help]; 13 | const rest = [file, edit, format, view, help]; 14 | 15 | module.exports = Menu.buildFromTemplate(is.darwin ? darwin : rest); 16 | -------------------------------------------------------------------------------- /src/menu/tray.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {shell} = require('electron'); 3 | const dialog = require('./../dialog'); 4 | const settings = require('./../settings'); 5 | const url = require('./../url'); 6 | const win = require('./../win'); 7 | 8 | module.exports = [ 9 | { 10 | label: 'Open Tusk', 11 | click() { 12 | win.toggle(); 13 | } 14 | }, { 15 | type: 'separator' 16 | }, { 17 | label: 'Search', 18 | click() { 19 | win.appear(); 20 | win.activate('search'); 21 | } 22 | }, { 23 | type: 'separator' 24 | }, { 25 | label: 'Create', 26 | submenu: [ 27 | { 28 | label: 'New Tag', 29 | click() { 30 | win.appear(); 31 | win.activate('new-tag'); 32 | } 33 | }, { 34 | label: 'New Note', 35 | click() { 36 | win.appear(); 37 | win.activate('new-note'); 38 | } 39 | }, { 40 | label: 'New Notebook', 41 | click() { 42 | win.appear(); 43 | win.activate('new-notebook'); 44 | } 45 | } 46 | ] 47 | }, { 48 | type: 'separator' 49 | }, { 50 | label: 'Toggle Theme', 51 | submenu: [ 52 | { 53 | label: 'Sepia Theme', 54 | click() { 55 | win.appear(); 56 | win.activate('toggle-sepia-mode'); 57 | } 58 | }, { 59 | label: 'Dark Theme', 60 | click() { 61 | win.appear(); 62 | win.activate('toggle-dark-mode'); 63 | } 64 | }, { 65 | label: 'Black Theme', 66 | click() { 67 | win.appear(); 68 | win.activate('toggle-black-mode'); 69 | } 70 | } 71 | ] 72 | }, { 73 | label: 'Auto Night Mode', 74 | type: 'checkbox', 75 | checked: settings.get('autoNightMode'), 76 | click(item) { 77 | win.appear(); 78 | settings.set('autoNightMode', item.checked); 79 | win.activate('auto-night-mode'); 80 | } 81 | }, { 82 | type: 'separator' 83 | }, { 84 | label: 'Evernote Settings', 85 | click() { 86 | shell.openExternal(url.settings); 87 | } 88 | }, { 89 | label: 'Report Issue', 90 | click() { 91 | shell.openExternal(url.issue); 92 | } 93 | }, { 94 | type: 'separator' 95 | }, { 96 | label: 'Exit', 97 | click() { 98 | dialog.confirmExit(); 99 | } 100 | } 101 | ]; 102 | -------------------------------------------------------------------------------- /src/menu/view.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {activate} = require('./../win'); 3 | const {is} = require('./../util'); 4 | const {setAcc} = require('./../keymap'); 5 | const dialog = require('./../dialog'); 6 | const settings = require('./../settings'); 7 | 8 | module.exports = { 9 | label: 'View', 10 | submenu: [ 11 | { 12 | label: 'Reload', 13 | accelerator: 'CmdOrCtrl+Shift+R', 14 | click: (_, focusedWindow) => { 15 | if (focusedWindow) { 16 | focusedWindow.reload(); 17 | } 18 | } 19 | }, { 20 | type: 'separator' 21 | }, { 22 | label: 'Font Size Options', 23 | submenu: [ 24 | { 25 | label: 'Make Text Larger', 26 | accelerator: 'CmdOrCtrl+Plus', 27 | click() { 28 | activate('zoom-in'); 29 | } 30 | }, { 31 | label: 'Make Text Smaller', 32 | accelerator: 'CmdOrCtrl+-', 33 | click() { 34 | activate('zoom-out'); 35 | } 36 | }, { 37 | label: 'Reset Zoom Level', 38 | accelerator: 'CmdOrCtrl+0', 39 | click() { 40 | activate('zoom-reset'); 41 | } 42 | } 43 | ] 44 | }, { 45 | type: 'separator' 46 | }, { 47 | label: 'Toggle Theme', 48 | submenu: [ 49 | { 50 | label: 'Sepia Theme', 51 | accelerator: setAcc('toggle-sepia-mode', 'CmdOrCtrl+G'), 52 | click() { 53 | activate('toggle-sepia-mode'); 54 | } 55 | }, { 56 | label: 'Dark Theme', 57 | accelerator: setAcc('toggle-dark-mode', 'CmdOrCtrl+D'), 58 | click() { 59 | activate('toggle-dark-mode'); 60 | } 61 | }, { 62 | label: 'Black Theme', 63 | accelerator: setAcc('toggle-black-mode', 'CmdOrCtrl+Alt+E'), 64 | click() { 65 | activate('toggle-black-mode'); 66 | } 67 | } 68 | ] 69 | }, { 70 | label: 'Auto Night Mode', 71 | type: 'checkbox', 72 | checked: settings.get('autoNightMode'), 73 | accelerator: 'CmdorCtrl+Alt+N', 74 | click(item) { 75 | settings.set('autoNightMode', item.checked); 76 | activate('auto-night-mode'); 77 | } 78 | }, { 79 | type: 'separator' 80 | }, { 81 | label: 'Navigate to Next Note', 82 | accelerator: 'CmdorCtrl+Tab', 83 | click() { 84 | activate('next-note'); 85 | } 86 | }, { 87 | label: 'Navigate to Previous Note', 88 | accelerator: 'CmdorCtrl+Shift+Tab', 89 | click() { 90 | activate('previous-note'); 91 | } 92 | }, { 93 | type: 'separator' 94 | }, { 95 | label: 'Always on Top', 96 | type: 'checkbox', 97 | checked: settings.get('alwaysOnTop'), 98 | accelerator: 'CmdorCtrl+Shift+P', 99 | click(item, focusedWindow) { 100 | settings.set('alwaysOnTop', item.checked); 101 | focusedWindow.setAlwaysOnTop(item.checked); 102 | } 103 | }, { 104 | label: 'Hide Tray Icon', 105 | type: 'checkbox', 106 | visible: !is.darwin, 107 | checked: settings.get('hideTray'), 108 | click(item) { 109 | dialog.confirmActivationRestart('hideTray', item.checked); 110 | item.checked = settings.get('hideTray'); 111 | } 112 | }, { 113 | type: 'separator' 114 | }, { 115 | label: 'Toggle Side Bar', 116 | type: 'checkbox', 117 | checked: !settings.get('sideBarHidden'), 118 | accelerator: setAcc('toggle-sidebar', 'CmdorCtrl+\\'), 119 | click() { 120 | activate('toggle-side-bar'); 121 | } 122 | }, { 123 | label: 'Toggle Menu Bar', 124 | type: 'checkbox', 125 | checked: !settings.get('menuBarHidden'), 126 | visible: !is.darwin, 127 | click(item, focusedWindow) { 128 | settings.set('menuBarHidden', !item.checked); 129 | focusedWindow.setMenuBarVisibility(item.checked); 130 | focusedWindow.setAutoHideMenuBar(!item.checked); 131 | } 132 | }, { 133 | label: 'Toggle Focus Mode', 134 | accelerator: setAcc('toggle-focus-mode', 'CmdOrCtrl+K'), 135 | click() { 136 | activate('focus-mode'); 137 | } 138 | }, { 139 | label: 'Toggle Full Screen', 140 | accelerator: is.darwin ? 'Ctrl+Command+F' : 'F11', 141 | click: (_, focusedWindow) => { 142 | if (focusedWindow) { 143 | focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); 144 | focusedWindow.send('window:fullscreen', {state: focusedWindow.isFullScreen()}); 145 | } 146 | } 147 | }, { 148 | label: 'Toggle Developer Tools', 149 | accelerator: is.darwin ? 'Alt+Command+I' : 'Ctrl+Shift+I', 150 | click: (_, focusedWindow) => { 151 | focusedWindow.toggleDevTools(); 152 | } 153 | } 154 | ] 155 | }; 156 | -------------------------------------------------------------------------------- /src/menu/window.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | role: 'window', 5 | submenu: [ 6 | { 7 | role: 'minimize' 8 | }, { 9 | role: 'close' 10 | }, { 11 | role: 'front' 12 | } 13 | ] 14 | }; 15 | -------------------------------------------------------------------------------- /src/mode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const nav = require('./nav'); 3 | const settings = require('./settings'); 4 | const time = require('./time'); 5 | 6 | class Mode { 7 | get _colors() { 8 | return { 9 | black: 'lightgrey', 10 | dark: 'lightgrey', 11 | default: 'black', 12 | sepia: 'black' 13 | }; 14 | } 15 | 16 | async _setFontColor(mode) { 17 | const frame = await this.getNoteFrame(); 18 | const style = document.createElement('style'); 19 | const fontColor = settings.get(`mode.${mode}`) ? this._colors[mode] : this._colors.default; 20 | style.textContent = `body {color: ${fontColor};}`; 21 | return frame.contentDocument.head.append(style); 22 | } 23 | 24 | _toggle(mode) { 25 | const modes = settings.get('mode'); 26 | 27 | Object.keys(modes).forEach(x => { 28 | settings.set(`mode.${x}`, (x === mode) ? !modes[x] : false); 29 | document.documentElement.classList.toggle(`${x}-mode`, settings.get(`mode.${x}`)); 30 | }); 31 | 32 | this._setFontColor(mode); 33 | } 34 | 35 | _enableAutoNight() { 36 | if (time.isDaytime()) { 37 | this._toggle(null); 38 | } else if (!settings.get('mode.dark')) { 39 | this._toggle('dark'); 40 | } 41 | 42 | setTimeout(() => { 43 | if (settings.get('autoNightMode')) { 44 | return this._enableAutoNight(); 45 | } 46 | }, time.ms(time.transitionSpan())); 47 | } 48 | 49 | _disableAutoNight() { 50 | this._toggle(null); 51 | } 52 | 53 | autoNight() { 54 | return settings.get('autoNightMode') ? this._enableAutoNight() : this._disableAutoNight(); 55 | } 56 | 57 | black() { 58 | this._toggle('black'); 59 | } 60 | 61 | dark() { 62 | this._toggle('dark'); 63 | } 64 | 65 | getNoteFrame() { 66 | return new Promise(resolve => { 67 | const checkNoteFrame = () => { 68 | const frame = nav.select('.RichTextArea-entinymce'); 69 | if (frame) { 70 | resolve(frame); 71 | } 72 | 73 | setTimeout(checkNoteFrame, 50); 74 | }; 75 | 76 | checkNoteFrame(); 77 | }); 78 | } 79 | 80 | restore() { 81 | const modes = settings.get('mode'); 82 | Object.keys(modes).forEach(x => { 83 | if (modes[x]) { 84 | this._setFontColor(x); 85 | document.documentElement.classList.toggle(`${x}-mode`, modes[x]); 86 | } 87 | }); 88 | } 89 | 90 | async autoWritingDirection() { 91 | const frame = await this.getNoteFrame(); 92 | frame.contentDocument.body.setAttribute('dir', 'auto'); 93 | document.querySelector('#gwt-debug-NoteTitleView-container').setAttribute('dir', 'auto'); 94 | } 95 | 96 | sepia() { 97 | this._toggle('sepia'); 98 | } 99 | 100 | toggleFocus() { 101 | const isFocused = document.querySelector('#gwt-debug-NoteAttributes-focusButton').style.length; 102 | 103 | if (isFocused) { 104 | return nav.click('#gwt-debug-NoteAttributes-doneButton'); 105 | } 106 | 107 | nav.click('#gwt-debug-NoteAttributes-focusButton'); 108 | } 109 | } 110 | 111 | module.exports = new Mode(); 112 | -------------------------------------------------------------------------------- /src/nav.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {webFrame} = require('electron'); 3 | const {is} = require('./util'); 4 | const settings = require('./settings'); 5 | 6 | class Nav { 7 | constructor() { 8 | this._defaultZoomFactor = 1.0; 9 | this._lowerZoomLimit = 0.7; 10 | this._noteSelector = '.focus-NotesView-Note'; 11 | this._notesList = '.NotesView-ScrollWindow > div'; 12 | this._notesListSelector = '.NotesView-ScrollWindow'; 13 | this._scrollStep = 110; 14 | this._selectedNoteSelector = '.focus-NotesView-Note-selected'; 15 | this._upperZoomLimit = 1.3; 16 | this._zoomStep = 0.05; 17 | } 18 | 19 | _currentIdx() { 20 | let currentIdx = 0; 21 | 22 | const selectedNote = document.querySelector(this._selectedNoteSelector); 23 | const notesArray = document.querySelector(this._notesListSelector).querySelectorAll(this._noteSelector); 24 | 25 | for (let i = 0; i < notesArray.length; i++) { 26 | if (notesArray[i] === selectedNote) { 27 | currentIdx = i + 1; 28 | } 29 | } 30 | 31 | return currentIdx; 32 | } 33 | 34 | _scrollUp() { 35 | const notesScrollbox = document.querySelector(this._notesListSelector); 36 | notesScrollbox.scrollTop -= this._scrollStep; 37 | } 38 | 39 | _scrollDown() { 40 | const notesScrollbox = document.querySelector(this._notesListSelector); 41 | notesScrollbox.scrollTop += this._scrollStep; 42 | } 43 | 44 | click(x) { 45 | document.querySelector(x).click(); 46 | } 47 | 48 | jumpToNote(event) { 49 | const comboKey = is.darwin ? event.metaKey : event.ctrlKey; 50 | 51 | if (!comboKey) { 52 | return null; 53 | } 54 | 55 | const n = parseInt(event.key, 10); 56 | 57 | if (n < 10 && n > 0) { 58 | this.selectNote(n); 59 | } 60 | } 61 | 62 | nextNote() { 63 | const idx = this._currentIdx(); 64 | this.selectNote(idx + 1); 65 | this._scrollDown(); 66 | } 67 | 68 | previousNote() { 69 | const idx = this._currentIdx(); 70 | this.selectNote(idx - 1); 71 | this._scrollUp(); 72 | } 73 | 74 | select(x) { 75 | return document.querySelector(x); 76 | } 77 | 78 | selectNote(index) { 79 | document.querySelector(this._notesList).children[index].firstChild.firstChild.click(); 80 | } 81 | 82 | sideBar() { 83 | document.documentElement.classList.toggle('side-bar-hidden', settings.get('sideBarHidden')); 84 | 85 | if (is.darwin) { 86 | // Macos visual tweak 87 | document.documentElement.classList.toggle('side-bar-hidden-macos', settings.get('sideBarHidden')); 88 | } 89 | } 90 | 91 | zoomIn() { 92 | const zoomFactor = webFrame.getZoomFactor() + this._zoomStep; 93 | 94 | if (zoomFactor < this._upperZoomLimit) { 95 | webFrame.setZoomFactor(zoomFactor); 96 | settings.set('zoomFactor', zoomFactor); 97 | } 98 | } 99 | 100 | zoomReset() { 101 | webFrame.setZoomFactor(this._defaultZoomFactor); 102 | settings.set('zoomFactor', this._defaultZoomFactor); 103 | } 104 | 105 | zoomRestore() { 106 | webFrame.setZoomFactor(settings.get('zoomFactor')); 107 | } 108 | 109 | zoomOut() { 110 | const zoomFactor = webFrame.getZoomFactor() - this._zoomStep; 111 | 112 | if (zoomFactor > this._lowerZoomLimit) { 113 | webFrame.setZoomFactor(zoomFactor); 114 | settings.set('zoomFactor', zoomFactor); 115 | } 116 | } 117 | } 118 | 119 | module.exports = new Nav(); 120 | -------------------------------------------------------------------------------- /src/pdf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {BrowserWindow, dialog, shell} = require('electron'); 3 | const {join} = require('path'); 4 | const {tmpdir} = require('os'); 5 | const {writeFile} = require('fs'); 6 | const {formatExternal, is} = require('./util'); 7 | const {stamp} = require('./time'); 8 | 9 | const {log} = console; 10 | 11 | class Pdf { 12 | get _opts() { 13 | return { 14 | filters: [ 15 | { 16 | name: 'PDF File', 17 | extensions: ['pdf'] 18 | }, { 19 | name: 'All Files', 20 | extensions: ['*'] 21 | } 22 | ] 23 | }; 24 | } 25 | 26 | print(event) { 27 | const path = join(tmpdir(), `Tusk_Note_${stamp()}.pdf`); 28 | const {webContents} = BrowserWindow.fromWebContents(event.sender); 29 | 30 | webContents.printToPDF({}, (error, data) => { 31 | if (error) { 32 | return log(error); 33 | } 34 | 35 | writeFile(path, data, error => { 36 | if (error) { 37 | return log(error); 38 | } 39 | 40 | shell.openExternal(formatExternal(path)); 41 | }); 42 | }); 43 | } 44 | 45 | save(event, title) { 46 | const opts = Object.assign(this._opts, {defaultPath: title}); 47 | const {webContents} = BrowserWindow.fromWebContents(event.sender); 48 | 49 | webContents.printToPDF({}, (error, data) => { 50 | if (error) { 51 | return log(error); 52 | } 53 | 54 | dialog.showSaveDialog(opts, path => { 55 | if (is.undef(path)) { 56 | return; 57 | } 58 | 59 | writeFile(path, data, error => { 60 | if (error) { 61 | return dialog.showErrorBox('Failed to export note as PDF', error.message); 62 | } 63 | }); 64 | }); 65 | }); 66 | } 67 | } 68 | 69 | module.exports = new Pdf(); 70 | -------------------------------------------------------------------------------- /src/save.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {writeFile} = require('fs'); 3 | const electron = require('electron'); 4 | const Turndown = require('turndown'); 5 | const {is} = require('./util'); 6 | const mode = require('./mode'); 7 | 8 | const {dialog} = electron.remote; 9 | const {log} = console; 10 | 11 | class Save { 12 | get _opts() { 13 | return { 14 | md: { 15 | filters: [ 16 | { 17 | name: 'Markdown File', 18 | extensions: ['md'] 19 | }, { 20 | name: 'All Files', 21 | extensions: ['*'] 22 | } 23 | ] 24 | }, 25 | html: { 26 | filters: [ 27 | { 28 | name: 'HTML File', 29 | extensions: ['html'] 30 | }, { 31 | name: 'All Files', 32 | extensions: ['*'] 33 | } 34 | ] 35 | } 36 | }; 37 | } 38 | 39 | _getTitle() { 40 | const title = document.querySelector('#gwt-debug-NoteTitleView-label').innerHTML; 41 | return title.length > 0 ? title.trim().replace(/ /g, ' ') : 'note'; 42 | } 43 | 44 | _toHTML(noteFrame) { 45 | return noteFrame.contentDocument.body.innerHTML; 46 | } 47 | 48 | _toMarkdown(noteFrame) { 49 | const turndownUtil = new Turndown(); 50 | return turndownUtil.turndown(noteFrame.contentDocument.body); 51 | } 52 | 53 | _save(opts, data) { 54 | const options = Object.assign(opts, {defaultPath: this._getTitle()}); 55 | 56 | dialog.showSaveDialog(options, path => { 57 | if (is.undef(path)) { 58 | return; 59 | } 60 | 61 | writeFile(path, data, error => { 62 | if (error) { 63 | dialog.showErrorBox('Failed to export note.', error.message); 64 | return log(error); 65 | } 66 | }); 67 | }); 68 | } 69 | 70 | async md() { 71 | const noteFrame = await mode.getNoteFrame(); 72 | return this._save(this._opts.md, this._toMarkdown(noteFrame)); 73 | } 74 | 75 | async html() { 76 | const noteFrame = await mode.getNoteFrame(); 77 | return this._save(this._opts.html, this._toHTML(noteFrame)); 78 | } 79 | } 80 | 81 | module.exports = new Save(); 82 | -------------------------------------------------------------------------------- /src/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const settings = require('electron-settings'); 3 | const {touchFileSync} = require('./util'); 4 | 5 | touchFileSync(settings.file()); 6 | 7 | settings.setAll({ 8 | alwaysOnTop: settings.get('alwaysOnTop', false), 9 | autoLaunch: settings.get('autoLaunch', false), 10 | autoNightMode: settings.get('autoNightMode', false), 11 | disableAutoUpdateCheck: settings.get('disableAutoUpdateCheck', false), 12 | hideTray: settings.get('hideTray', false), 13 | lastWindowState: { 14 | x: settings.get('lastWindowState.x'), 15 | y: settings.get('lastWindowState.y'), 16 | width: settings.get('lastWindowState.width'), 17 | height: settings.get('lastWindowState.height') 18 | }, 19 | launchMinimized: settings.get('launchMinimized', false), 20 | menuBarHidden: settings.get('menuBarHidden', false), 21 | mode: { 22 | black: settings.get('mode.black', false), 23 | dark: settings.get('mode.dark', false), 24 | sepia: settings.get('mode.sepia', false) 25 | }, 26 | requestExitConfirmation: settings.get('requestExitConfirmation', true), 27 | sideBarHidden: settings.get('sideBarHidden', false), 28 | updateCheckPeriod: settings.get('updateCheckPeriod', '4'), 29 | useGlobalShortcuts: settings.get('useGlobalShortcuts', false), 30 | useYinxiang: settings.get('useYinxiang', false), 31 | zoomFactor: settings.get('zoomFactor', 1) 32 | }); 33 | 34 | module.exports = settings; 35 | -------------------------------------------------------------------------------- /src/startup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {app} = require('electron'); 3 | const AutoLaunch = require('auto-launch'); 4 | const {is} = require('./util'); 5 | const settings = require('./settings'); 6 | 7 | const _settings = { 8 | name: 'Tusk', 9 | path: is.darwin ? app.getPath('exe').replace(/\.app\/Content.*/, '.app') : undefined, 10 | isHidden: true 11 | }; 12 | 13 | class Startup { 14 | constructor(settings) { 15 | this._launcher = new AutoLaunch(settings); 16 | } 17 | 18 | async _activate() { 19 | const enabled = await this._launcher.isEnabled(); 20 | if (!enabled) { 21 | return this._launcher.enable(); 22 | } 23 | } 24 | 25 | async _deactivate() { 26 | const enabled = await this._launcher.isEnabled(); 27 | if (enabled) { 28 | return this._launcher.disable(); 29 | } 30 | } 31 | 32 | autoLaunch() { 33 | if (settings.get('autoLaunch')) { 34 | this._activate(); 35 | } else { 36 | this._deactivate(); 37 | } 38 | } 39 | } 40 | 41 | module.exports = new Startup(_settings); 42 | -------------------------------------------------------------------------------- /src/style/black-mode.css: -------------------------------------------------------------------------------- 1 | html.black-mode ::-webkit-scrollbar-track { /* Notes list scrollbar track */ 2 | background: var(--black) !important; 3 | } 4 | html.black-mode div#gwt-debug-NotesHeader-title, html.black-mode div#gwt-debug-NoteAttributesView-root, html.black-mode div#gwt-debug-NotebooksDrawerView-root, html.black-mode div#gwt-debug-AccountMenuPopup-root, html.black-mode div#gwt-debug-arrowChatView, html.black-mode div#gwt-debug-ReminderSetDialog-root { 5 | background-color: var(--black) !important; 6 | } 7 | html.black-mode .GJDCG5CJEB { /* Metting note background */ 8 | background-color: var(--black) !important; 9 | } 10 | html.black-mode .GJDCG5COYB { /* Note list header */ 11 | background-color: var(--black) !important; 12 | } 13 | html.black-mode .GJDCG5CAR.GJDCG5CBR, html.black-mode .GJDCG5CCR { 14 | opacity: 1 !important; 15 | } 16 | html.black-mode .Dropdown-menu { 17 | background: var(--black) !important; 18 | } 19 | html.black-mode .GJDCG5CMGB, html.black-mode .GDAMOPFCAHB, html.black-mode .GDAMOPFCPGB, html.black-mode .GDAMOPFCHYC.GDAMOPFCLD, html.black-mode .GJDCG5CFKB { /* Chat windows */ 20 | background: var(--black) !important; 21 | } 22 | html.black-mode .GJDCG5CKKC, html.black-mode .GJDCG5CK-B { /* Reminder modal */ 23 | background: var(--black) !important; 24 | } 25 | html.black-mode .GDAMOPFCHEB { 26 | background: var(--black) !important; 27 | } 28 | html.black-mode .GJDCG5CJ-B, html.black-mode .GDAMOPFCEQB, html.black-mode .GJDCG5CGQB, html.black-mode .GDAMOPFCDAC.GDAMOPFCM5B, html.black-mode .GJDCG5CO-B .GJDCG5CD1B, html.black-mode .GJDCG5CL-B .GJDCG5CP0B:hover { /* Search modal */ 29 | background-color: var(--black) !important; 30 | } 31 | html.black-mode .GJDCG5CK-B, html.black-mode .GJDCG5CP-B, html.black-mode .GJDCG5CDXB { 32 | border-bottom: 1px solid var(--white-five) !important; 33 | } 34 | html.black-mode .GJDCG5CEXB { 35 | border-bottom: 1px solid var(--black) !important; 36 | } 37 | html.black-mode .GJDCG5CGWB, html.black-mode .GDAMOPFCIXB, html.black-mode .GJDCG5CKXB.GJDCG5CHXB, html.black-mode .GDAMOPFCFTB { /* Notebooks modal */ 38 | background-color: var(--black) !important; 39 | } 40 | html.black-mode .GJDCG5CDXB.GJDCG5CPXB { /* Selected notebook in notebooks modal */ 41 | background-color: #2DBE60 !important; 42 | } 43 | html.black-mode .GJDCG5CITB { /* Select notebook at toolbar option */ 44 | background-color: var(--black) !important; 45 | } 46 | html.black-mode .GJDCG5CJJ, html.black-mode .focus-drawer-Filter-input { /* Search notebook/tags input text */ 47 | color: #E1E1E1 !important; 48 | } 49 | html.black-mode div#gwt-debug-GlassModalDialog-container { /* Glass modal background */ 50 | background-color: var(--black) !important; 51 | } 52 | html.black-mode div#gwt-debug-GlassModalDialog-glass { /* Glass modal bottom line */ 53 | background-color: var(--black) !important; 54 | } 55 | html.black-mode .GDAMOPFCPE { /* Notebook info */ 56 | color: #E1E1E1 !important; 57 | } 58 | html.black-mode .GDAMOPFCN-B .GDAMOPFCC1B, html.black-mode .GDAMOPFCDQB { /* Saved search */ 59 | background-color: var(--black) !important; 60 | } 61 | html.black-mode .GJDCG5CJ3B, html.black-mode .GJDCG5CI3B, html.black-mode .GJDCG5CH3B, html.black-mode .GJDCG5CN5B, html.black-mode .GJDCG5CGHB, html.black-mode .GJDCG5CFHB, html.black-mode .GJDCG5CM5B { /* Shortcuts modal */ 62 | background-color: var(--black) !important; 63 | border-right: 3px solid var(--white-five) !important; 64 | } 65 | html.black-mode .GJDCG5CP2 { /* Settings menu */ 66 | background-color: var(--black) !important; 67 | } 68 | html.black-mode .GJDCG5CB5B { 69 | background: var(--black) !important; 70 | background-color: var(--black) !important; 71 | border-right: 1px solid var(--white-five) !important; 72 | } 73 | html.black-mode .GDAMOPFCJYB { /* Notes list head */ 74 | background-color: var(--black) !important; 75 | } 76 | html.black-mode .GJDCG5CBQB { /* Notes list left border */ 77 | background-color: var(--black) !important; 78 | border-left: 1px solid var(--white-five) !important; 79 | } 80 | html.black-mode .GJDCG5CB-B { 81 | background-color: var(--black) !important; 82 | } 83 | html.black-mode .GDAMOPFCA5B { /* Notes list left border */ 84 | border-left: 1px solid var(--white-five) !important; 85 | border-right: 1px solid var(--white-five) !important; 86 | background: var(--black) !important; 87 | background-color: var(--black) !important; 88 | } 89 | html.black-mode .focus-NotesView-Note.focus-NotesView-Note-selected .focus-NotesView-Note-selectOverlay { /* Selected note bordered box */ 90 | border: 3px solid var(--white-five) !important; 91 | } 92 | html.black-mode .focus-NotesView-RemindersList { /* Reminders list borders */ 93 | border-top: 2px solid var(--white-five) !important; 94 | background-color: var(--black) !important; 95 | border-bottom: 3px solid var(--white-five) !important; 96 | } 97 | html.black-mode .focus-NotesView-NotebookReminders-DropAreaContainer { /* Notebook reminder background */ 98 | background: var(--black) !important; 99 | } 100 | html.black-mode .focus-NotesView-Subheader { /* Subheader border */ 101 | border-bottom: 1px solid var(--white-five) !important; 102 | } 103 | html.black-mode .GJDCG5CLLB { 104 | background-color: var(--black) !important; 105 | } 106 | html.black-mode .GJDCG5CJKB, html.black-mode .GJDCG5CA0B { /* Toolbar bottom border */ 107 | border-bottom: 1px solid var(--white-five) !important; 108 | } 109 | html.black-mode .GDAMOPFCILB, html.black-mode .GJDCG5CAOB { /* Note attributes background */ 110 | background: var(--black) !important; 111 | } 112 | html.black-mode .GJDCG5CP0, html.black-mode .GJDCG5CLKB, html.black-mode .GJDCG5CCQB { /* Toolbar separators */ 113 | background: var(--black) !important; 114 | border-left: 1px solid var(--white-five) !important; 115 | } 116 | html.black-mode .focus-NotesView-Note .focus-NotesView-Note-snippetDivider { /* Notes divider color */ 117 | border-top: 1px solid var(--white-five) !important; 118 | } 119 | html.black-mode .GJDCG5CO3B { /* Profile background */ 120 | background-color: var(--black) !important; 121 | border-top: 1px solid var(--white-five) !important; 122 | } 123 | html.black-mode .GDAMOPFCPYB { /* Note list transition background */ 124 | background-color: var(--black) !important; 125 | border-top: 1px solid var(--white-five) !important; 126 | } 127 | html.black-mode .GDAMOPFCMYB { /* Note list header */ 128 | background-color: var(--black) !important; 129 | margin-bottom: -6px !important; 130 | } 131 | html.black-mode div#gwt-debug-FocusView-root { /* Sidebar transition background */ 132 | background-color: var(--black) !important; 133 | } 134 | html.black-mode .focus-NotesView-NotesView { /* Notes list body */ 135 | background-color: var(--black) !important; 136 | } 137 | html.black-mode .GJDCG5CNNB, html.black-mode .GDAMOPFCOPB { /* Note editor body */ 138 | background-color: var(--black) !important; 139 | } 140 | html.black-mode .GJDCG5CKKB, html.black-mode .GJDCG5CI1 { /* Note editor toolbar */ 141 | background-color: var(--black) !important; 142 | border-bottom: 1px solid var(--black) !important; 143 | } 144 | html.black-mode .GJDCG5CMKB, html.black-mode .GJDCG5CO0 { 145 | border-left: 1px solid var(--black) !important; 146 | } 147 | html.black-mode .GJDCG5CJ1 { /* Note editor toolbar options */ 148 | background-color: var(--black) !important; 149 | } 150 | html.black-mode .GJDCG5CONB { /* Note editor background */ 151 | background-color: var(--black) !important; 152 | } 153 | html.black-mode .GJDCG5COF { /* Colors background */ 154 | border: 1px solid var(--black) !important; 155 | background-color: var(--black) !important; 156 | } 157 | html.black-mode .GJDCG5CHK, html.black-mode .GJDCG5CGK, html.black-mode .GJDCG5CGK.GJDCG5CAK, html.black-mode .GJDCG5CNQ.GJDCG5CPQ , html.black-mode .GJDCG5CIEB.GJDCG5CBFB, html.black-mode .GJDCG5CM1 { /* Submenus background */ 158 | background-color: var(--black) !important; 159 | } 160 | html.black-mode .GDAMOPFCNHC { /* Dialogs background */ 161 | background-color: var(--black) !important; 162 | } 163 | html.black-mode .GJDCG5CFLB { /* Note editor head */ 164 | background-color: var(--black) !important; 165 | } 166 | html.black-mode div#imggal-glass { /* Image gallery background*/ 167 | background-color: var(--black) !important; 168 | } 169 | html.black-mode #imggal-top .close-icon { /* Image gallery exit button */ 170 | border-radius: 0% !important; /* Makes the button square */ 171 | background-color: var(--black) !important; 172 | } 173 | -------------------------------------------------------------------------------- /src/style/browser.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --black: rgba(0, 0, 0, 1); 3 | --sepia: rgba(251, 240, 217, 1); 4 | --dark-grey: rgba(33, 33, 33, 1); 5 | --black-five: rgba(0, 0, 0, 0.05); 6 | --white-five: rgba(255, 255, 255, 0.05); 7 | } 8 | html { 9 | overflow: hidden; 10 | } 11 | body { 12 | -webkit-app-region: drag; 13 | } 14 | ::-webkit-scrollbar { /* Notes list scrollbar */ 15 | width: 6px !important; 16 | } 17 | ::-webkit-scrollbar-track { /* Notes list scrollbar track */ 18 | border-radius: 0px !important; 19 | } 20 | ::-webkit-scrollbar-thumb { /* Notes list scrollbar thumb */ 21 | border-radius: 0px !important; 22 | background: #2DBE60 !important; 23 | } 24 | #switch-link { /* Allow text selection on Macos */ 25 | -webkit-app-region: no-drag; 26 | } 27 | div#gwt-debug-NoteView-root { 28 | -webkit-app-region: no-drag; 29 | } 30 | div#gwt-debug-notesListView { 31 | -webkit-app-region: no-drag; 32 | } 33 | div#gwt-debug-FocusView-root { /* Main app window background */ 34 | background-color: white !important; 35 | } 36 | .GJDCG5CO4B { /* Elephant logo */ 37 | left: 0; 38 | right: 0; 39 | bottom: 0; 40 | top: 25px; 41 | height: 66px; 42 | position: relative; 43 | } 44 | .GJDCG5CF5B { /* Sidebar buttons */ 45 | margin: 20px 0px !important; 46 | } 47 | .FocusClipperButton-button { 48 | visibility: hidden !important; 49 | } 50 | .focus-NotesView-NotesView { 51 | background-color: #FFFFFF !important; 52 | } 53 | .focus-NotesView-RemindersList { 54 | background-color: #FFFFFF; 55 | } 56 | .GJDCG5COYB { /* Note list header */ 57 | background-color: #FFFFFF; 58 | margin-bottom: -6px !important; 59 | } 60 | .GJDCG5CO3B { /* Profile background */ 61 | background-color: #F8F8F8 !important; 62 | } 63 | div#gwt-debug-NotesHeaderView-root { 64 | background-color: #FFFFFF !important; 65 | } 66 | div#gwt-debug-NotesHeader-title { /* Notes list title */ 67 | padding-bottom: 10px !important; 68 | } 69 | html.side-bar-hidden div#gwt-debug-sidebar { /* Make side bar visible */ 70 | display: none !important; 71 | } 72 | html.side-bar-hidden div#gwt-debug-stage { 73 | margin-left: 0px !important; 74 | } 75 | html.side-bar-hidden-macos div#gwt-debug-NotesHeader-title { 76 | padding-top: 13px !important; 77 | } 78 | @media all and (max-width: 800px) { 79 | div#gwt-debug-NoteView-root { 80 | margin-left: 0px !important; 81 | } 82 | div#gwt-debug-notesListView { /* Auto-hide note list */ 83 | display: none !important; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/style/dark-mode.css: -------------------------------------------------------------------------------- 1 | html.dark-mode ::-webkit-scrollbar-track { /* Notes list scrollbar track */ 2 | background: var(--dark-grey) !important; 3 | } 4 | html.dark-mode div#gwt-debug-NotesHeader-title, html.dark-mode div#gwt-debug-NoteAttributesView-root, html.dark-mode div#gwt-debug-NotebooksDrawerView-root, html.dark-mode div#gwt-debug-AccountMenuPopup-root, html.dark-mode div#gwt-debug-arrowChatView, html.dark-mode div#gwt-debug-ReminderSetDialog-root { 5 | background-color: var(--dark-grey) !important; 6 | } 7 | html.dark-mode .GJDCG5CJEB { /* Metting note background */ 8 | background-color: var(--dark-grey) !important; 9 | } 10 | html.dark-mode .GJDCG5COYB { /* Note list header */ 11 | background-color: var(--dark-grey) !important; 12 | } 13 | html.dark-mode .GJDCG5CAR.GJDCG5CBR, html.dark-mode .GJDCG5CCR { 14 | opacity: 1 !important; 15 | } 16 | html.dark-mode .Dropdown-menu { 17 | background: var(--dark-grey) !important; 18 | } 19 | html.dark-mode .GJDCG5CMGB, html.dark-mode .GDAMOPFCAHB, html.dark-mode .GDAMOPFCPGB, html.dark-mode .GDAMOPFCHYC.GDAMOPFCLD, html.dark-mode .GJDCG5CFKB { /* Chat windows */ 20 | background: var(--dark-grey) !important; 21 | } 22 | html.dark-mode .GJDCG5CKKC, html.dark-mode .GJDCG5CK-B { /* Reminder modal */ 23 | background: var(--dark-grey) !important; 24 | } 25 | html.dark-mode .GDAMOPFCHEB { 26 | background: var(--dark-grey) !important; 27 | } 28 | html.dark-mode .GJDCG5CJ-B, html.dark-mode .GDAMOPFCEQB, html.dark-mode .GJDCG5CGQB, html.dark-mode .GDAMOPFCDAC.GDAMOPFCM5B, html.dark-mode .GJDCG5CO-B .GJDCG5CD1B, html.dark-mode .GJDCG5CL-B .GJDCG5CP0B:hover { /* Search modal */ 29 | background-color: var(--dark-grey) !important; 30 | } 31 | html.dark-mode .GJDCG5CK-B, html.dark-mode .GJDCG5CP-B, html.dark-mode .GJDCG5CDXB { 32 | border-bottom: 1px solid var(--white-five) !important; 33 | } 34 | html.dark-mode .GJDCG5CEXB { 35 | border-bottom: 1px solid var(--dark-grey) !important; 36 | } 37 | html.dark-mode .GJDCG5CGWB, html.dark-mode .GDAMOPFCIXB, html.dark-mode .GJDCG5CKXB.GJDCG5CHXB, html.dark-mode .GDAMOPFCFTB { /* Notebooks modal */ 38 | background-color: var(--dark-grey) !important; 39 | } 40 | html.dark-mode .GJDCG5CDXB.GJDCG5CPXB { /* Selected notebook in notebooks modal */ 41 | background-color: #2DBE60 !important; 42 | } 43 | html.dark-mode .GJDCG5CITB { /* Select notebook at toolbar option */ 44 | background-color: var(--dark-grey) !important; 45 | } 46 | html.dark-mode .GJDCG5CJJ, html.dark-mode .focus-drawer-Filter-input { /* Search notebook/tags input text */ 47 | color: #E1E1E1 !important; 48 | } 49 | html.dark-mode div#gwt-debug-GlassModalDialog-container { /* Glass modal background */ 50 | background-color: var(--dark-grey) !important; 51 | } 52 | html.dark-mode div#gwt-debug-GlassModalDialog-glass { /* Glass modal bottom line */ 53 | background-color: var(--dark-grey) !important; 54 | } 55 | html.dark-mode .GDAMOPFCPE { /* Notebook info */ 56 | color: #E1E1E1 !important; 57 | } 58 | html.dark-mode .GDAMOPFCN-B .GDAMOPFCC1B, html.dark-mode .GDAMOPFCDQB { /* Saved search */ 59 | background-color: var(--dark-grey) !important; 60 | } 61 | html.dark-mode .GJDCG5CJ3B, html.dark-mode .GJDCG5CI3B, html.dark-mode .GJDCG5CH3B, html.dark-mode .GJDCG5CN5B, html.dark-mode .GJDCG5CGHB, html.dark-mode .GJDCG5CFHB, html.dark-mode .GJDCG5CM5B { /* Shortcuts modal */ 62 | background-color: var(--dark-grey) !important; 63 | border-right: 3px solid var(--white-five) !important; 64 | } 65 | html.dark-mode .GJDCG5CP2 { /* Settings menu */ 66 | background-color: var(--dark-grey) !important; 67 | } 68 | html.dark-mode .GJDCG5CB5B { 69 | background: var(--dark-grey) !important; 70 | background-color: var(--dark-grey) !important; 71 | border-right: 1px solid var(--white-five) !important; 72 | } 73 | html.dark-mode .GDAMOPFCJYB { /* Notes list head */ 74 | background-color: var(--dark-grey) !important; 75 | } 76 | html.dark-mode .GJDCG5CBQB { /* Notes list left border */ 77 | background-color: var(--dark-grey) !important; 78 | border-left: 1px solid var(--white-five) !important; 79 | } 80 | html.dark-mode .GJDCG5CB-B { 81 | background-color: var(--dark-grey) !important; 82 | } 83 | html.dark-mode .GDAMOPFCA5B { /* Notes list left border */ 84 | border-left: 1px solid var(--white-five) !important; 85 | border-right: 1px solid var(--white-five) !important; 86 | background: var(--dark-grey) !important; 87 | background-color: var(--dark-grey) !important; 88 | } 89 | html.dark-mode .focus-NotesView-Note.focus-NotesView-Note-selected .focus-NotesView-Note-selectOverlay { /* Selected note bordered box */ 90 | border: 3px solid var(--white-five) !important; 91 | } 92 | html.dark-mode .focus-NotesView-RemindersList { /* Reminders list borders */ 93 | border-top: 2px solid var(--white-five) !important; 94 | border-bottom: 3px solid var(--white-five) !important; 95 | background-color: var(--dark-grey) !important; 96 | } 97 | html.dark-mode .focus-NotesView-NotebookReminders-DropAreaContainer { /* Notebook reminder background */ 98 | background: var(--dark-grey) !important; 99 | } 100 | html.dark-mode .focus-NotesView-Subheader { /* Subheader border */ 101 | border-bottom: 1px solid var(--white-five) !important; 102 | } 103 | html.dark-mode .GJDCG5CLLB { 104 | background-color: var(--dark-grey) !important; 105 | } 106 | html.dark-mode .GJDCG5CJKB, html.dark-mode .GJDCG5CA0B { /* Toolbar bottom border */ 107 | border-bottom: 1px solid var(--white-five) !important; 108 | } 109 | html.dark-mode .GDAMOPFCILB, html.dark-mode .GJDCG5CAOB { /* Note attributes background */ 110 | background: var(--dark-grey) !important; 111 | } 112 | html.dark-mode .GJDCG5CP0, html.dark-mode .GJDCG5CLKB, html.dark-mode .GJDCG5CCQB { /* Toolbar separators */ 113 | background: var(--dark-grey) !important; 114 | border-left: 1px solid var(--white-five) !important; 115 | } 116 | html.dark-mode .focus-NotesView-Note .focus-NotesView-Note-snippetDivider { /* Notes divider color */ 117 | border-top: 1px solid var(--white-five) !important; 118 | } 119 | html.dark-mode .GJDCG5CO3B { /* Profile background */ 120 | background-color: var(--dark-grey) !important; 121 | border-top: 1px solid var(--white-five) !important; 122 | } 123 | html.dark-mode .GDAMOPFCPYB { /* Note list transition background */ 124 | background-color: var(--dark-grey) !important; 125 | border-top: 1px solid var(--white-five) !important; 126 | } 127 | html.dark-mode .GDAMOPFCMYB { /* Note list header */ 128 | background-color: var(--dark-grey) !important; 129 | margin-bottom: -6px !important; 130 | } 131 | html.dark-mode div#gwt-debug-FocusView-root { /* Sidebar transition background */ 132 | background-color: var(--dark-grey) !important; 133 | } 134 | html.dark-mode .focus-NotesView-NotesView { /* Notes list body */ 135 | background-color: var(--dark-grey) !important; 136 | } 137 | html.dark-mode .GJDCG5CNNB, html.dark-mode .GDAMOPFCOPB { /* Note editor body */ 138 | background-color: var(--dark-grey) !important; 139 | } 140 | html.dark-mode .GJDCG5CKKB, html.dark-mode .GJDCG5CI1 { 141 | /* Note editor toolbar */ 142 | background-color: var(--dark-grey) !important; 143 | border-bottom: 1px solid var(--dark-grey) !important; 144 | } 145 | html.dark-mode .GJDCG5CMKB, html.dark-mode .GJDCG5CO0 { 146 | border-left: 1px solid var(--dark-grey) !important; 147 | } 148 | html.dark-mode .GJDCG5CJ1 { /* Note editor toolbar options */ 149 | background-color: var(--dark-grey) !important; 150 | } 151 | html.dark-mode .GJDCG5CONB { /* Note editor background */ 152 | background-color: var(--dark-grey) !important; 153 | } 154 | html.dark-mode .GJDCG5COF { /* Colors background */ 155 | border: 1px solid var(--dark-grey) !important; 156 | background-color: var(--dark-grey) !important; 157 | } 158 | html.dark-mode .GJDCG5CHK, html.dark-mode .GJDCG5CGK, html.dark-mode .GJDCG5CGK.GJDCG5CAK, html.dark-mode .GJDCG5CNQ.GJDCG5CPQ , html.dark-mode .GJDCG5CIEB.GJDCG5CBFB, html.dark-mode .GJDCG5CM1 { /* Submenus background */ 159 | background-color: var(--dark-grey) !important; 160 | } 161 | html.dark-mode .GDAMOPFCNHC { /* Dialogs background */ 162 | background-color: var(--dark-grey) !important; 163 | } 164 | html.dark-mode .GJDCG5CFLB { /* Note editor head */ 165 | background-color: var(--dark-grey) !important; 166 | } 167 | html.dark-mode div#imggal-glass { /* Image gallery background*/ 168 | background-color: var(--dark-grey) !important; 169 | } 170 | html.dark-mode #imggal-top .close-icon { /* Image gallery exit button */ 171 | border-radius: 0% !important; /* Makes the button square */ 172 | background-color: var(--dark-grey) !important; 173 | } 174 | -------------------------------------------------------------------------------- /src/style/sepia-mode.css: -------------------------------------------------------------------------------- 1 | html.sepia-mode ::-webkit-scrollbar-track { /* Notes list scrollbar track */ 2 | background: var(--sepia) !important; 3 | } 4 | html.sepia-mode div#gwt-debug-NotesHeader-title, html.sepia-mode div#gwt-debug-NoteAttributesView-root, html.sepia-mode div#gwt-debug-NotebooksDrawerView-root, html.sepia-mode div#gwt-debug-AccountMenuPopup-root, html.sepia-mode div#gwt-debug-arrowChatView, html.sepia-mode div#gwt-debug-ReminderSetDialog-root { 5 | background-color: var(--sepia) !important; 6 | } 7 | html.sepia-mode .GJDCG5CJEB { /* Metting note background */ 8 | background-color: var(--sepia) !important; 9 | } 10 | html.sepia-mode .GJDCG5COYB { /* Note list header */ 11 | background-color: var(--sepia) !important; 12 | } 13 | html.sepia-mode .GJDCG5CAR.GJDCG5CBR, html.sepia-mode .GJDCG5CCR { 14 | opacity: 1 !important; 15 | } 16 | html.sepia-mode .Dropdown-menu { 17 | background: var(--sepia) !important; 18 | } 19 | html.sepia-mode .GJDCG5CMGB, html.sepia-mode .GDAMOPFCAHB, html.sepia-mode .GDAMOPFCPGB, html.sepia-mode .GDAMOPFCHYC.GDAMOPFCLD, html.sepia-mode .GJDCG5CFKB { /* Chat windows */ 20 | background: var(--sepia) !important; 21 | } 22 | html.sepia-mode .GJDCG5CKKC, html.sepia-mode .GJDCG5CK-B { /* Reminder modal */ 23 | background: var(--sepia) !important; 24 | } 25 | html.sepia-mode .GDAMOPFCHEB { 26 | background: var(--sepia) !important; 27 | } 28 | html.sepia-mode .GJDCG5CJ-B, html.sepia-mode .GDAMOPFCEQB, html.sepia-mode .GJDCG5CGQB, html.sepia-mode .GDAMOPFCDAC.GDAMOPFCM5B, html.sepia-mode .GJDCG5CO-B .GJDCG5CD1B, html.sepia-mode .GJDCG5CL-B .GJDCG5CP0B:hover { /* Search modal */ 29 | background-color: var(--sepia) !important; 30 | } 31 | html.sepia-mode .GJDCG5CK-B, html.sepia-mode .GJDCG5CP-B, html.sepia-mode .GJDCG5CDXB { 32 | border-bottom: 1px solid var(--black-five) !important; 33 | } 34 | html.sepia-mode .GJDCG5CEXB { 35 | border-bottom: 1px solid var(--sepia) !important; 36 | } 37 | html.sepia-mode .GJDCG5CGWB, html.sepia-mode .GDAMOPFCIXB, html.sepia-mode .GJDCG5CKXB.GJDCG5CHXB, html.sepia-mode .GDAMOPFCFTB { /* Notebooks modal */ 38 | background-color: var(--sepia) !important; 39 | } 40 | html.sepia-mode .GJDCG5CDXB.GJDCG5CPXB { /* Selected notebook in notebooks modal */ 41 | background-color: #2DBE60 !important; 42 | } 43 | html.sepia-mode .GJDCG5CITB { /* Select notebook at toolbar option */ 44 | background-color: var(--sepia) !important; 45 | } 46 | html.sepia-mode .GJDCG5CJJ, html.sepia-mode .focus-drawer-Filter-input { /* Search notebook/tags input text */ 47 | color: #E1E1E1 !important; 48 | } 49 | html.sepia-mode div#gwt-debug-GlassModalDialog-container { /* Glass modal background */ 50 | background-color: var(--sepia) !important; 51 | } 52 | html.sepia-mode div#gwt-debug-GlassModalDialog-glass { /* Glass modal bottom line */ 53 | background-color: var(--sepia) !important; 54 | } 55 | html.sepia-mode .GDAMOPFCPE { /* Notebook info */ 56 | color: #E1E1E1 !important; 57 | } 58 | html.sepia-mode .GDAMOPFCN-B .GDAMOPFCC1B, html.sepia-mode .GDAMOPFCDQB { /* Saved search */ 59 | background-color: var(--sepia) !important; 60 | } 61 | html.sepia-mode .GJDCG5CJ3B, html.sepia-mode .GJDCG5CI3B, html.sepia-mode .GJDCG5CH3B, html.sepia-mode .GJDCG5CN5B, html.sepia-mode .GJDCG5CGHB, html.sepia-mode .GJDCG5CFHB, html.sepia-mode .GJDCG5CM5B { /* Shortcuts modal */ 62 | background-color: var(--sepia) !important; 63 | border-right: 3px solid var(--black-five) !important; 64 | } 65 | html.sepia-mode .GJDCG5CP2 { /* Settings menu */ 66 | background-color: var(--sepia) !important; 67 | } 68 | html.sepia-mode .GJDCG5CB5B { 69 | background: var(--sepia) !important; 70 | background-color: var(--sepia) !important; 71 | border-right: 1px solid var(--black-five) !important; 72 | } 73 | html.sepia-mode .GDAMOPFCJYB { /* Notes list head */ 74 | background-color: var(--sepia) !important; 75 | } 76 | html.sepia-mode .GJDCG5CBQB { /* Notes list left border */ 77 | background-color: var(--sepia) !important; 78 | border-left: 1px solid var(--black-five) !important; 79 | } 80 | html.sepia-mode .GJDCG5CB-B { 81 | background-color: var(--sepia) !important; 82 | } 83 | html.sepia-mode .GDAMOPFCA5B { /* Notes list left border */ 84 | border-left: 1px solid var(--black-five) !important; 85 | border-right: 1px solid var(--black-five) !important; 86 | background: var(--sepia) !important; 87 | background-color: var(--sepia) !important; 88 | } 89 | html.sepia-mode .focus-NotesView-Note.focus-NotesView-Note-selected .focus-NotesView-Note-selectOverlay { /* Selected note bordered box */ 90 | border: 3px solid var(--black-five) !important; 91 | } 92 | html.sepia-mode .focus-NotesView-RemindersList { /* Reminders list borders */ 93 | background-color: var(--sepia) !important; 94 | border-top: 2px solid var(--black-five) !important; 95 | border-bottom: 3px solid var(--black-five) !important; 96 | } 97 | html.sepia-mode .focus-NotesView-NotebookReminders-DropAreaContainer { /* Notebook reminder background */ 98 | background: var(--sepia) !important; 99 | } 100 | html.sepia-mode .focus-NotesView-Subheader { /* Subheader border */ 101 | border-bottom: 1px solid var(--black-five) !important; 102 | } 103 | html.sepia-mode .GJDCG5CLLB { 104 | background-color: var(--sepia) !important; 105 | } 106 | html.sepia-mode .GJDCG5CJKB, html.sepia-mode .GJDCG5CA0B { /* Toolbar bottom border */ 107 | border-bottom: 1px solid var(--black-five) !important; 108 | } 109 | html.sepia-mode .GDAMOPFCILB, html.sepia-mode .GJDCG5CAOB { /* Note attributes background */ 110 | background: var(--sepia) !important; 111 | } 112 | html.sepia-mode .GJDCG5CP0, html.sepia-mode .GJDCG5CLKB, html.sepia-mode .GJDCG5CCQB { /* Toolbar separators */ 113 | background: var(--sepia) !important; 114 | border-left: 1px solid var(--black-five) !important; 115 | } 116 | html.sepia-mode .focus-NotesView-Note .focus-NotesView-Note-snippetDivider { /* Notes divider color */ 117 | border-top: 1px solid var(--black-five) !important; 118 | } 119 | html.sepia-mode .GJDCG5CO3B { /* Profile background */ 120 | background-color: var(--sepia) !important; 121 | border-top: 1px solid var(--black-five) !important; 122 | } 123 | html.sepia-mode .GDAMOPFCPYB { /* Note list transition background */ 124 | background-color: var(--sepia) !important; 125 | border-top: 1px solid var(--black-five) !important; 126 | } 127 | html.sepia-mode .GDAMOPFCMYB { /* Note list header */ 128 | background-color: var(--sepia) !important; 129 | margin-bottom: -6px !important; 130 | } 131 | html.sepia-mode div#gwt-debug-FocusView-root { /* Sidebar transition background */ 132 | background-color: var(--sepia) !important; 133 | } 134 | html.sepia-mode .focus-NotesView-NotesView { /* Notes list body */ 135 | background-color: var(--sepia) !important; 136 | } 137 | html.sepia-mode .GJDCG5CNNB, html.sepia-mode .GDAMOPFCOPB { /* Note editor body */ 138 | background-color: var(--sepia) !important; 139 | } 140 | html.sepia-mode .GJDCG5CKKB, html.sepia-mode .GJDCG5CI1 { /* Note editor toolbar */ 141 | background-color: var(--sepia) !important; 142 | border-bottom: 1px solid var(--sepia) !important; 143 | } 144 | html.sepia-mode .GJDCG5CMKB, html.sepia-mode .GJDCG5CO0 { 145 | border-left: 1px solid var(--sepia) !important; 146 | } 147 | html.sepia-mode .GJDCG5CJ1 { /* Note editor toolbar options */ 148 | background-color: var(--sepia) !important; 149 | } 150 | html.sepia-mode .GJDCG5CONB { /* Note editor background */ 151 | background-color: var(--sepia) !important; 152 | } 153 | html.sepia-mode .GJDCG5COF { /* Colors background */ 154 | border: 1px solid var(--sepia) !important; 155 | background-color: var(--sepia) !important; 156 | } 157 | html.sepia-mode .GJDCG5CHK, html.sepia-mode .GJDCG5CGK, html.sepia-mode .GJDCG5CGK.GJDCG5CAK, html.sepia-mode .GJDCG5CNQ.GJDCG5CPQ , html.sepia-mode .GJDCG5CIEB.GJDCG5CBFB, html.sepia-mode .GJDCG5CM1 { /* Submenus background */ 158 | background-color: var(--sepia) !important; 159 | } 160 | html.sepia-mode .GDAMOPFCNHC { /* Dialogs background */ 161 | background-color: var(--sepia) !important; 162 | } 163 | html.sepia-mode .GJDCG5CFLB { /* Note editor head */ 164 | background-color: var(--sepia) !important; 165 | } 166 | html.sepia-mode div#imggal-glass { /* Image gallery background*/ 167 | background-color: var(--sepia) !important; 168 | } 169 | html.sepia-mode #imggal-top .close-icon { /* Image gallery exit button */ 170 | border-radius: 0% !important; /* Makes the button square */ 171 | background-color: var(--sepia) !important; 172 | } 173 | -------------------------------------------------------------------------------- /src/time.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Time { 4 | date() { 5 | const _ = new Date(); 6 | return [_.getMonth() + 1, _.getDate(), _.getFullYear()].join('/'); 7 | } 8 | 9 | dateTime() { 10 | return `${this.date()} ${this.time()}`; 11 | } 12 | 13 | hours() { 14 | return new Date().getHours(); 15 | } 16 | 17 | isDaytime() { 18 | const hs = this.hours(); 19 | return hs < 18 && hs > 6; 20 | } 21 | 22 | ms(hours) { 23 | return 1000 * 60 * 60 * parseInt(hours, 10); 24 | } 25 | 26 | stamp() { 27 | const _ = new Date(); 28 | return [ 29 | _.getMonth(), 30 | _.getDay(), 31 | _.getFullYear(), 32 | _.getHours(), 33 | _.getMinutes(), 34 | _.getSeconds() 35 | ].join('-'); 36 | } 37 | 38 | time() { 39 | const _ = new Date(); 40 | return [_.getHours(), _.getMinutes(), _.getSeconds()].join(':'); 41 | } 42 | 43 | transitionSpan() { 44 | const hs = this.hours(); 45 | return this.isDaytime() ? 18 - hs : (hs < 6 ? 6 - hs : 30 - hs); 46 | } 47 | } 48 | 49 | module.exports = new Time(); 50 | -------------------------------------------------------------------------------- /src/tray.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const electron = require('electron'); 3 | const {is} = require('./util'); 4 | const file = require('./file'); 5 | const tpl = require('./menu/tray'); 6 | const win = require('./win'); 7 | 8 | const {app, Menu} = electron; 9 | 10 | class Tray { 11 | constructor() { 12 | this._tray = null; 13 | } 14 | 15 | create() { 16 | if (is.darwin) { 17 | return; 18 | } 19 | 20 | this._tray = new electron.Tray(file.trayIcon); 21 | this._tray.setToolTip(app.getName()); 22 | this._tray.setContextMenu(Menu.buildFromTemplate(tpl)); 23 | this._tray.on('click', win.toggle); 24 | } 25 | } 26 | 27 | module.exports = new Tray(); 28 | -------------------------------------------------------------------------------- /src/update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {app} = require('electron'); 3 | const {get} = require('https'); 4 | const dialog = require('./dialog'); 5 | const url = require('./url'); 6 | 7 | const {log} = console; 8 | 9 | class Update { 10 | _compareToLocal(version) { 11 | const [x, y] = [version, app.getVersion()].map(x => x.split('.').map(Number)); 12 | 13 | for (let i = 0; i < 3; i++) { 14 | const dif = x[i] - y[i]; 15 | if (dif !== 0) { 16 | return dif; 17 | } 18 | } 19 | 20 | return 0; 21 | } 22 | 23 | _fetchUpdateData() { 24 | return new Promise((resolve, reject) => { 25 | const request = get(url.update, res => { 26 | const {statusCode: sc} = res; 27 | 28 | if (sc < 200 || sc > 299) { 29 | reject(new Error(`Request to get update data failed with HTTP status code: ${sc}`)); 30 | } 31 | 32 | const data = []; 33 | res.on('data', d => data.push(d)); 34 | res.on('end', () => resolve(JSON.parse(data.join('')))); 35 | }); 36 | 37 | request.on('error', err => reject(err)); 38 | }); 39 | } 40 | 41 | _hasUpdate(version) { 42 | return this._compareToLocal(version) > 0; 43 | } 44 | 45 | async auto() { 46 | let latestVer; 47 | 48 | try { 49 | const data = await this._fetchUpdateData(); 50 | latestVer = data.version; 51 | } catch (error) { 52 | return log(error); 53 | } 54 | 55 | if (this._hasUpdate(latestVer)) { 56 | return dialog.getUpdate(latestVer); 57 | } 58 | } 59 | 60 | async check() { 61 | let latestVer; 62 | 63 | try { 64 | const data = await this._fetchUpdateData(); 65 | latestVer = data.version; 66 | } catch (error) { 67 | return dialog.updateError(error.message); 68 | } 69 | 70 | if (this._hasUpdate(latestVer)) { 71 | return dialog.getUpdate(latestVer); 72 | } 73 | 74 | return dialog.noUpdate(); 75 | } 76 | } 77 | 78 | module.exports = new Update(); 79 | -------------------------------------------------------------------------------- /src/url.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | community: 'https://gitter.im/klaussinani/tusk', 5 | evernote: 'https://www.evernote.com/Login.action', 6 | homepage: 'https://klaussinani.github.io/tusk', 7 | issue: 'https://github.com/klaussinani/tusk/issues/new', 8 | keyboardShortcutsRef: 'https://github.com/klaussinani/tusk#keyboard-shortcuts', 9 | license: 'https://github.com/klaussinani/tusk/blob/master/license.md', 10 | redirect: 'https://www.evernote.com/OutboundRedirect.action?dest=', 11 | release: 'https://github.com/klaussinani/tusk/releases/latest', 12 | search: 'https://github.com/search?q=+is:issue+repo:klaussinani/tusk', 13 | searchFeatureRequests: 'https://github.com/klaussinani/tusk/labels/feature-request', 14 | settings: 'https://www.evernote.com/Settings.action', 15 | source: 'https://github.com/klaussinani/tusk', 16 | update: 'https://raw.githubusercontent.com/klaussinani/tusk/master/docs/update.json', 17 | yinxiang: 'https://app.yinxiang.com/Login.action', 18 | yinxiangRedirect: 'https://app.yinxiang.com/OutboundRedirect.action?dest=' 19 | }; 20 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {dirname, join} = require('path'); 3 | const fs = require('fs'); 4 | const decodeUri = require('decode-uri-component'); 5 | const url = require('./url'); 6 | 7 | const {log} = console; 8 | const {platform} = process; 9 | 10 | class Util { 11 | get is() { 12 | return { 13 | darwin: platform === 'darwin', 14 | downloadURL: x => x.split('/', 4).includes('shard'), 15 | linux: platform === 'linux', 16 | undef: x => x === undefined, 17 | win32: platform === 'win32' 18 | }; 19 | } 20 | 21 | ensureFileSync(path, data) { 22 | if (!fs.existsSync(path)) { 23 | try { 24 | fs.writeFileSync(path, data); 25 | } catch (error) { 26 | log(error); 27 | } 28 | } 29 | } 30 | 31 | formatExternal(x) { 32 | return `file://${x}`; 33 | } 34 | 35 | formatTitle(x) { 36 | return x.replace(' | Evernote Web', ''); 37 | } 38 | 39 | formatURL(x) { 40 | return decodeUri(x.replace(url.redirect, '')); 41 | } 42 | 43 | formatYinxiangURL(x) { 44 | return decodeUri(x.replace(url.yinxiangRedirect, '')); 45 | } 46 | 47 | readSheet(x) { 48 | return fs.readFileSync(join(__dirname, './style', x), 'utf8'); 49 | } 50 | 51 | touchFileSync(x) { 52 | const dir = dirname(x); 53 | 54 | if (!fs.existsSync(dir)) { 55 | fs.mkdirSync(dir); 56 | } 57 | 58 | return fs.closeSync(fs.openSync(x, 'a')); 59 | } 60 | } 61 | module.exports = new Util(); 62 | -------------------------------------------------------------------------------- /src/win.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {join} = require('path'); 3 | const electron = require('electron'); 4 | const {is} = require('./util'); 5 | const file = require('./file'); 6 | const settings = require('./settings'); 7 | 8 | const {app, BrowserWindow} = electron; 9 | 10 | class Win { 11 | get _screenDimensions() { 12 | const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize; 13 | return [width, height]; 14 | } 15 | 16 | get _defaultDimensions() { 17 | return this._screenDimensions.map(x => Math.round(x * 0.9)); 18 | } 19 | 20 | get _lastState() { 21 | const {x, y, width, height} = settings.get('lastWindowState'); 22 | const [defaultWidth, defaultHeight] = this._defaultDimensions; 23 | 24 | return { 25 | x, 26 | y, 27 | width: width || defaultWidth, 28 | height: height || defaultHeight 29 | }; 30 | } 31 | 32 | get _minDimensions() { 33 | const [minWidth, minHeight] = this._screenDimensions.map(x => Math.round(x * 0.3)); 34 | return {minWidth, minHeight}; 35 | } 36 | 37 | get defaultOpts() { 38 | return Object.assign({}, this._minDimensions, this._lastState, { 39 | alwaysOnTop: settings.get('alwaysOnTop'), 40 | autoHideMenuBar: settings.get('menuBarHidden'), 41 | darkTheme: settings.get('mode.dark') || settings.get('mode.black'), 42 | icon: is.linux && file.icon, 43 | show: false, 44 | title: app.getName(), 45 | titleBarStyle: 'hiddenInset', 46 | webPreferences: { 47 | nodeIntegration: false, 48 | plugins: true, 49 | preload: join(__dirname, './browser.js') 50 | } 51 | }); 52 | } 53 | 54 | activate(command) { 55 | const [win] = BrowserWindow.getAllWindows(); 56 | 57 | if (is.darwin) { 58 | win.restore(); 59 | } 60 | 61 | win.webContents.send(command); 62 | } 63 | 64 | appear() { 65 | const [win] = BrowserWindow.getAllWindows(); 66 | if (!win.isVisible() || !win.isFocused()) { 67 | win.show(); 68 | win.focus(); 69 | } 70 | } 71 | 72 | toggle() { 73 | const [win] = BrowserWindow.getAllWindows(); 74 | if (win.isVisible()) { 75 | win.hide(); 76 | } else { 77 | win.show(); 78 | win.focus(); 79 | } 80 | } 81 | } 82 | 83 | module.exports = new Win(); 84 | -------------------------------------------------------------------------------- /static/Icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/static/Icon.icns -------------------------------------------------------------------------------- /static/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/static/Icon.ico -------------------------------------------------------------------------------- /static/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/static/Icon.png -------------------------------------------------------------------------------- /static/IconTray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/static/IconTray.png -------------------------------------------------------------------------------- /static/IconTray@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaudiosinani/tusk/5d06732b7186e4e2cf3e7686d75be2f1e9be8e34/static/IconTray@2x.png --------------------------------------------------------------------------------