├── .babelrc
├── .codeclimate.yml
├── .editorconfig
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .gitmodules
├── .jshintrc
├── .modernizrrc
├── .travis.yml
├── CONTRIBUTE.md
├── LICENSE
├── README.md
├── app
├── .htaccess
├── 404.html
├── config.xml
├── docs
│ └── howto.md
├── favicon.ico
├── images
│ ├── icon
│ │ ├── IconMenubarTemplate.png
│ │ ├── IconMenubarTemplate@2x.png
│ │ ├── icon-120x120.png
│ │ ├── icon-128x128.png
│ │ ├── icon-150x150.png
│ │ ├── icon-152x152.png
│ │ ├── icon-16x16.png
│ │ ├── icon-310x150.png
│ │ ├── icon-310x310.png
│ │ ├── icon-36x36.png
│ │ ├── icon-48x48.png
│ │ ├── icon-512x512.icns
│ │ ├── icon-512x512.png
│ │ ├── icon-70x70.png
│ │ ├── icon-72x72.png
│ │ ├── icon-76x76.png
│ │ ├── icon-96x96.png
│ │ └── icon.png
│ └── laverna.icon.xcf
├── index.html
├── locales
│ ├── ar
│ │ └── translation.json
│ ├── bs_ba
│ │ └── translation.json
│ ├── da
│ │ └── translation.json
│ ├── de
│ │ └── translation.json
│ ├── de_ch
│ │ └── translation.json
│ ├── el
│ │ └── translation.json
│ ├── en
│ │ └── translation.json
│ ├── eo
│ │ └── translation.json
│ ├── es
│ │ └── translation.json
│ ├── fr
│ │ └── translation.json
│ ├── gl
│ │ └── translation.json
│ ├── hi_in
│ │ └── translation.json
│ ├── it
│ │ └── translation.json
│ ├── ja
│ │ └── translation.json
│ ├── ko
│ │ └── translation.json
│ ├── locales.json
│ ├── lt
│ │ └── translation.json
│ ├── lv
│ │ └── translation.json
│ ├── mr_in
│ │ └── translation.json
│ ├── nb
│ │ └── translation.json
│ ├── nl
│ │ └── translation.json
│ ├── nn
│ │ └── translation.json
│ ├── oc
│ │ └── translation.json
│ ├── pl
│ │ └── translation.json
│ ├── pt
│ │ └── translation.json
│ ├── pt_br
│ │ └── translation.json
│ ├── ru
│ │ └── translation.json
│ ├── se
│ │ └── translation.json
│ ├── sq
│ │ └── translation.json
│ ├── tr
│ │ └── translation.json
│ ├── zh_cn
│ │ └── translation.json
│ └── zh_tw
│ │ └── translation.json
├── manifest.webapp
├── robots.txt
├── scripts
│ ├── App.js
│ ├── behaviors
│ │ ├── Content.js
│ │ ├── ModalForm.js
│ │ ├── ModelFocus.js
│ │ ├── Navigate.js
│ │ ├── Pagination.js
│ │ ├── Sidebar.js
│ │ └── Sidemenu.js
│ ├── collections
│ │ ├── Collection.js
│ │ ├── Configs.js
│ │ ├── DiffCollection.js
│ │ ├── Edits.js
│ │ ├── Files.js
│ │ ├── Notebooks.js
│ │ ├── Notes.js
│ │ ├── Pageable.js
│ │ ├── Profiles.js
│ │ ├── Shadows.js
│ │ ├── Tags.js
│ │ ├── Users.js
│ │ ├── collections-new
│ │ │ ├── Collection.js
│ │ │ ├── Configs.js
│ │ │ └── configNames.js
│ │ ├── configNames.js
│ │ └── modules
│ │ │ ├── Configs.js
│ │ │ ├── Edits.js
│ │ │ ├── Files.js
│ │ │ ├── Module.js
│ │ │ ├── Notebooks.js
│ │ │ ├── Notes.js
│ │ │ ├── Profiles.js
│ │ │ ├── Shadows.js
│ │ │ ├── Tags.js
│ │ │ ├── Users.js
│ │ │ └── main.js
│ ├── components
│ │ ├── codemirror
│ │ │ ├── Controller.js
│ │ │ ├── Editor.js
│ │ │ ├── View.js
│ │ │ ├── main.js
│ │ │ └── template.html
│ │ ├── confirm
│ │ │ ├── Controller.js
│ │ │ ├── View.js
│ │ │ └── template.html
│ │ ├── dropbox
│ │ │ ├── Adapter.js
│ │ │ ├── Sync.js
│ │ │ ├── main.js
│ │ │ └── settings
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ ├── electronSearch
│ │ │ ├── Controller.js
│ │ │ ├── View.js
│ │ │ └── template.html
│ │ ├── encryption
│ │ │ ├── auth
│ │ │ │ ├── Controller.js
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ │ ├── encrypt
│ │ │ │ ├── Controller.js
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ │ └── main.js
│ │ ├── fileDialog
│ │ │ ├── Controller.js
│ │ │ ├── View.js
│ │ │ ├── main.js
│ │ │ └── templates
│ │ │ │ ├── dialog.html
│ │ │ │ └── dropzone.html
│ │ ├── fuzzySearch
│ │ │ ├── Controller.js
│ │ │ ├── main.js
│ │ │ ├── template.html
│ │ │ └── views
│ │ │ │ ├── Child.js
│ │ │ │ ├── Region.js
│ │ │ │ └── View.js
│ │ ├── help
│ │ │ ├── about
│ │ │ │ ├── Controller.js
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ │ ├── controller.js
│ │ │ └── keybindings
│ │ │ │ ├── Controller.js
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ ├── importExport
│ │ │ ├── Export.js
│ │ │ ├── Import.js
│ │ │ ├── ImportEvernote.js
│ │ │ ├── main.js
│ │ │ └── migrate
│ │ │ │ ├── Controller.js
│ │ │ │ ├── Encryption.js
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ ├── linkDialog
│ │ │ ├── Controller.js
│ │ │ ├── main.js
│ │ │ ├── templates
│ │ │ │ ├── item.html
│ │ │ │ └── template.html
│ │ │ └── views
│ │ │ │ ├── Collection.js
│ │ │ │ ├── Item.js
│ │ │ │ └── View.js
│ │ ├── markdown
│ │ │ ├── Markdown.js
│ │ │ ├── file.js
│ │ │ ├── main.js
│ │ │ └── task.js
│ │ ├── navbar
│ │ │ ├── Controller.js
│ │ │ ├── View.js
│ │ │ └── template.html
│ │ ├── notebooks
│ │ │ ├── Router.js
│ │ │ ├── controller.js
│ │ │ ├── form
│ │ │ │ ├── notebook
│ │ │ │ │ ├── Controller.js
│ │ │ │ │ ├── View.js
│ │ │ │ │ └── template.html
│ │ │ │ └── tag
│ │ │ │ │ ├── Controller.js
│ │ │ │ │ ├── View.js
│ │ │ │ │ └── template.html
│ │ │ ├── list
│ │ │ │ ├── Controller.js
│ │ │ │ ├── templates
│ │ │ │ │ ├── layout.html
│ │ │ │ │ ├── notebook.html
│ │ │ │ │ └── tag.html
│ │ │ │ └── views
│ │ │ │ │ ├── ItemView.js
│ │ │ │ │ ├── Layout.js
│ │ │ │ │ ├── Notebook.js
│ │ │ │ │ ├── Notebooks.js
│ │ │ │ │ ├── Tag.js
│ │ │ │ │ └── Tags.js
│ │ │ └── remove
│ │ │ │ └── Controller.js
│ │ ├── notes
│ │ │ ├── Router.js
│ │ │ ├── controller.js
│ │ │ ├── form
│ │ │ │ ├── Controller.js
│ │ │ │ ├── templates
│ │ │ │ │ ├── form.html
│ │ │ │ │ └── notebooks.html
│ │ │ │ └── views
│ │ │ │ │ ├── Form.js
│ │ │ │ │ ├── Notebook.js
│ │ │ │ │ ├── Notebooks.js
│ │ │ │ │ └── NotebooksCollection.js
│ │ │ ├── list
│ │ │ │ ├── Controller.js
│ │ │ │ ├── templates
│ │ │ │ │ ├── layout.html
│ │ │ │ │ └── noteView.html
│ │ │ │ └── views
│ │ │ │ │ ├── Layout.js
│ │ │ │ │ ├── NoteView.js
│ │ │ │ │ └── NotesView.js
│ │ │ ├── remove
│ │ │ │ └── Controller.js
│ │ │ └── show
│ │ │ │ ├── Controller.js
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ ├── settings
│ │ │ ├── Router.js
│ │ │ ├── controller.js
│ │ │ ├── show
│ │ │ │ ├── Behavior.js
│ │ │ │ ├── Controller.js
│ │ │ │ ├── View.js
│ │ │ │ ├── editor
│ │ │ │ │ ├── View.js
│ │ │ │ │ └── template.html
│ │ │ │ ├── encryption
│ │ │ │ │ ├── Key.js
│ │ │ │ │ ├── Passphrase.js
│ │ │ │ │ ├── View.js
│ │ │ │ │ ├── key.html
│ │ │ │ │ ├── passphrase.html
│ │ │ │ │ └── template.html
│ │ │ │ ├── general
│ │ │ │ │ ├── View.js
│ │ │ │ │ └── template.html
│ │ │ │ ├── importExport
│ │ │ │ │ ├── View.js
│ │ │ │ │ └── template.html
│ │ │ │ ├── keybindings
│ │ │ │ │ ├── View.js
│ │ │ │ │ └── template.html
│ │ │ │ ├── sync
│ │ │ │ │ ├── Users.js
│ │ │ │ │ ├── View.js
│ │ │ │ │ ├── template.html
│ │ │ │ │ └── users.html
│ │ │ │ ├── template.html
│ │ │ │ └── wipe
│ │ │ │ │ ├── View.js
│ │ │ │ │ ├── confirm.html
│ │ │ │ │ └── template.html
│ │ │ └── sidebar
│ │ │ │ ├── Controller.js
│ │ │ │ ├── templates
│ │ │ │ ├── navbar.html
│ │ │ │ └── template.html
│ │ │ │ └── views
│ │ │ │ ├── Navbar.js
│ │ │ │ └── View.js
│ │ ├── setup
│ │ │ ├── ContentView.js
│ │ │ ├── Controller.js
│ │ │ ├── View.js
│ │ │ ├── export
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ │ ├── main.js
│ │ │ ├── register
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ │ ├── template.html
│ │ │ └── username
│ │ │ │ ├── View.js
│ │ │ │ └── template.html
│ │ └── share
│ │ │ ├── Controller.js
│ │ │ ├── View.js
│ │ │ ├── info
│ │ │ ├── View.js
│ │ │ └── template.html
│ │ │ ├── template.html
│ │ │ └── users
│ │ │ ├── View.js
│ │ │ └── template.html
│ ├── constants.js
│ ├── main.js
│ ├── models
│ │ ├── Config.js
│ │ ├── Db.js
│ │ ├── Edit.js
│ │ ├── Encryption.js
│ │ ├── File.js
│ │ ├── Model.js
│ │ ├── Note.js
│ │ ├── Notebook.js
│ │ ├── Peer.js
│ │ ├── Profile.js
│ │ ├── Shadow.js
│ │ ├── Signal.js
│ │ ├── Sync.js
│ │ ├── Tag.js
│ │ ├── User.js
│ │ └── diffsync
│ │ │ ├── Core.js
│ │ │ ├── Diff.js
│ │ │ ├── Model.js
│ │ │ ├── Patch.js
│ │ │ └── main.js
│ ├── modules
│ │ ├── fs
│ │ │ ├── classes
│ │ │ │ ├── adapter.js
│ │ │ │ └── sync.js
│ │ │ ├── module.js
│ │ │ ├── templates
│ │ │ │ └── settings.html
│ │ │ └── views
│ │ │ │ └── settings.js
│ │ ├── mathjax
│ │ │ ├── libs
│ │ │ │ └── mathjax.js
│ │ │ └── module.js
│ │ ├── modules.json
│ │ └── remotestorage
│ │ │ ├── classes
│ │ │ ├── module.js
│ │ │ ├── rs.js
│ │ │ └── sync.js
│ │ │ └── module.js
│ ├── templates
│ │ ├── layout.html
│ │ └── loader.html
│ ├── utils
│ │ ├── Env.js
│ │ ├── I18n.js
│ │ ├── Initializer.js
│ │ ├── Keybindings.js
│ │ ├── Notify.js
│ │ ├── Title.js
│ │ ├── Url.js
│ │ ├── electronListener.js
│ │ ├── fileSaver.js
│ │ ├── theme.js
│ │ └── underscore.js
│ ├── views
│ │ ├── Brand.js
│ │ ├── Layout.js
│ │ ├── Loader.js
│ │ └── Modal.js
│ └── workers
│ │ ├── Delegator.js
│ │ ├── Module.js
│ │ └── worker.js
└── styles
│ ├── core
│ ├── bootstrap.less
│ ├── codemirror.less
│ ├── codemirror
│ │ ├── core.less
│ │ └── theme.less
│ ├── editor.less
│ ├── fontello.less
│ ├── fontello
│ │ ├── LICENSE.txt
│ │ ├── README.txt
│ │ ├── config.json
│ │ ├── css
│ │ │ ├── animation.less
│ │ │ ├── fontello-codes.less
│ │ │ ├── fontello-embedded.less
│ │ │ ├── fontello-ie7-codes.less
│ │ │ ├── fontello-ie7.less
│ │ │ └── fontello.less
│ │ ├── demo.html
│ │ └── font
│ │ │ ├── fontello.eot
│ │ │ ├── fontello.svg
│ │ │ ├── fontello.ttf
│ │ │ └── fontello.woff
│ ├── fuzzy.less
│ ├── header.less
│ ├── layout.less
│ ├── list.less
│ ├── main.less
│ ├── responsive.less
│ ├── sidemenu.less
│ ├── utils.less
│ └── variables.less
│ ├── theme-dark
│ ├── main.less
│ ├── prism.less
│ └── theme.less
│ ├── theme-default
│ ├── buttons.less
│ ├── checkbox.less
│ ├── codemirror.less
│ ├── dropzone.less
│ ├── editor.less
│ ├── forms.less
│ ├── header.less
│ ├── layout.less
│ ├── list.less
│ ├── loading-animation.less
│ ├── main.less
│ ├── modal.less
│ ├── prism.less
│ ├── settings.less
│ ├── sidemenu.less
│ ├── utils.less
│ └── variables.less
│ └── themes.json
├── config.xml
├── electron
├── electron.js
├── package-lock.json
├── package.json
└── server.js
├── gulpfile.js
├── gulps
├── bundle.js
├── clean.js
├── copy.js
├── copyDist.js
├── copyRelease.js
├── css.js
├── electron.js
├── html.js
├── lav-server.js
├── lint.js
├── mobile.js
├── nightwatch.js
├── npm.js
├── serve-hosted.js
├── serve.js
└── test.js
├── package.json
├── test
├── nightwatch.json
├── spec-ui
│ ├── commands
│ │ ├── addNote.js
│ │ ├── addNotebook.js
│ │ ├── addTag.js
│ │ ├── changeEncryption.js
│ │ ├── closeWelcome.js
│ │ └── findAll.js
│ ├── modules
│ │ └── remotestorage
│ │ │ ├── auth.js
│ │ │ ├── client1.js
│ │ │ └── client2.js
│ └── tests
│ │ ├── apps
│ │ ├── encryption
│ │ │ └── encrypt.js
│ │ ├── navbar
│ │ │ └── navbar.js
│ │ ├── notebooks
│ │ │ ├── form.js
│ │ │ ├── formEdit.js
│ │ │ ├── list.js
│ │ │ └── remove.js
│ │ ├── notes
│ │ │ ├── form.js
│ │ │ ├── list.js
│ │ │ └── show.js
│ │ ├── settings
│ │ │ ├── general.js
│ │ │ ├── import.js
│ │ │ ├── keybindings.js
│ │ │ └── profiles.js
│ │ └── tags
│ │ │ ├── form.js
│ │ │ ├── formEdit.js
│ │ │ ├── list.js
│ │ │ └── remove.js
│ │ └── modules
│ │ └── fuzzySearch
│ │ └── fuzzySearch.js
└── tape
│ ├── app.js
│ ├── behaviors
│ ├── content.js
│ ├── modalForm.js
│ ├── modelFocus.js
│ ├── navigate.js
│ ├── pagination.js
│ ├── sidebar.js
│ └── sidemenu.js
│ ├── collections
│ ├── collection.js
│ ├── configs.js
│ ├── edits.js
│ ├── files.js
│ ├── modules
│ │ ├── configs.js
│ │ ├── edits.js
│ │ ├── files.js
│ │ ├── module.js
│ │ ├── notebooks.js
│ │ ├── notes.js
│ │ ├── profiles.js
│ │ ├── shadows.js
│ │ ├── tags.js
│ │ └── users.js
│ ├── notebooks.js
│ ├── notes.js
│ ├── pageable.js
│ ├── profiles.js
│ ├── shadows.js
│ ├── tags.js
│ └── users.js
│ ├── components
│ ├── codemirror
│ │ ├── controller.js
│ │ ├── editor.js
│ │ └── view.js
│ ├── confirm
│ │ ├── controller.js
│ │ └── view.js
│ ├── dropbox
│ │ ├── adapter.js
│ │ ├── main.js
│ │ ├── settings
│ │ │ └── view.js
│ │ └── sync.js
│ ├── electronSearch
│ │ ├── controller.js
│ │ └── view.js
│ ├── encryption
│ │ ├── auth
│ │ │ ├── controller.js
│ │ │ └── view.js
│ │ ├── encrypt
│ │ │ ├── controller.js
│ │ │ └── view.js
│ │ └── main.js
│ ├── fileDialog
│ │ ├── controller.js
│ │ ├── main.js
│ │ └── view.js
│ ├── fuzzySearch
│ │ ├── child.js
│ │ ├── controller.js
│ │ ├── main.js
│ │ ├── region.js
│ │ └── view.js
│ ├── help
│ │ ├── about
│ │ │ ├── controller.js
│ │ │ └── view.js
│ │ ├── controller.js
│ │ └── keybindings
│ │ │ ├── controller.js
│ │ │ └── view.js
│ ├── importExport
│ │ ├── backup.enex
│ │ ├── export.js
│ │ ├── import.js
│ │ ├── importEvernote.js
│ │ ├── main.js
│ │ └── migrate
│ │ │ ├── controller.js
│ │ │ ├── encryption.js
│ │ │ └── view.js
│ ├── linkDialog
│ │ ├── collection.js
│ │ ├── controller.js
│ │ ├── item.js
│ │ ├── main.js
│ │ └── view.js
│ ├── markdown
│ │ ├── file.js
│ │ ├── markdown.js
│ │ └── task.js
│ ├── navbar
│ │ ├── controller.js
│ │ └── view.js
│ ├── notebooks
│ │ ├── controller.js
│ │ ├── form
│ │ │ ├── notebook
│ │ │ │ ├── controller.js
│ │ │ │ └── view.js
│ │ │ └── tag
│ │ │ │ ├── controller.js
│ │ │ │ └── view.js
│ │ ├── list
│ │ │ ├── controller.js
│ │ │ ├── itemView.js
│ │ │ ├── layout.js
│ │ │ ├── notebook.js
│ │ │ ├── notebooks.js
│ │ │ ├── tag.js
│ │ │ └── tags.js
│ │ ├── remove
│ │ │ └── controller.js
│ │ └── router.js
│ ├── notes
│ │ ├── controller.js
│ │ ├── form
│ │ │ ├── controller.js
│ │ │ └── views
│ │ │ │ ├── form.js
│ │ │ │ ├── notebook.js
│ │ │ │ ├── notebooks.js
│ │ │ │ └── notebooksCollection.js
│ │ ├── list
│ │ │ ├── controller.js
│ │ │ ├── layout.js
│ │ │ ├── noteView.js
│ │ │ └── notesView.js
│ │ ├── remove
│ │ │ └── controller.js
│ │ ├── router.js
│ │ └── show
│ │ │ ├── controller.js
│ │ │ └── view.js
│ ├── settings
│ │ ├── controller.js
│ │ ├── router.js
│ │ ├── show
│ │ │ ├── behavior.js
│ │ │ ├── controller.js
│ │ │ ├── editor.js
│ │ │ ├── encryption.js
│ │ │ ├── general.js
│ │ │ ├── importExport.js
│ │ │ ├── key.js
│ │ │ ├── keybindings.js
│ │ │ ├── passphrase.js
│ │ │ ├── sync.js
│ │ │ ├── users.js
│ │ │ └── view.js
│ │ └── sidebar
│ │ │ ├── controller.js
│ │ │ └── view.js
│ ├── setup
│ │ ├── contentView.js
│ │ ├── controller.js
│ │ ├── export.js
│ │ ├── main.js
│ │ ├── register.js
│ │ ├── username.js
│ │ └── view.js
│ └── share
│ │ ├── controller.js
│ │ ├── info.js
│ │ ├── users.js
│ │ └── view.js
│ ├── index.js
│ ├── models
│ ├── config.js
│ ├── db.js
│ ├── diffsync
│ │ ├── core.js
│ │ ├── diff.js
│ │ ├── main.js
│ │ ├── model.js
│ │ ├── patch-diff.js
│ │ └── patch.js
│ ├── edit.js
│ ├── encryption.js
│ ├── file.js
│ ├── model.js
│ ├── note.js
│ ├── notebook.js
│ ├── peer.js
│ ├── profile.js
│ ├── shadow.js
│ ├── signal.js
│ ├── sync.js
│ ├── tag.js
│ └── user.js
│ ├── overrideTemplate.js
│ ├── utils
│ ├── electronListener.js
│ ├── env.js
│ ├── i18n.js
│ ├── initializer.js
│ ├── keybindings.js
│ ├── notify.js
│ ├── theme.js
│ ├── title.js
│ ├── underscore.js
│ └── url.js
│ ├── views
│ ├── brand.js
│ ├── layout.js
│ └── modal.js
│ └── workers
│ ├── delegator.js
│ ├── module.js
│ └── worker.js
├── webpack.config.js
├── webpack.production.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["env", {
3 | "targets": {
4 | "browsers": [
5 | "last 2 Chrome versions"
6 | ]
7 | }
8 | }]]
9 | }
10 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | eslint:
3 | enabled: true
4 | duplication:
5 | enabled: true
6 | config :
7 | languages:
8 | - javascript
9 |
10 | languages:
11 | JavaScript: true
12 |
13 | ratings:
14 | paths:
15 | - app/scripts/**
16 |
17 | exclude_paths:
18 | - test/**
19 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 4
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/.gitmodules
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 4,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "jquery": true,
22 | "mocha": true,
23 | "globals": {
24 | "expect": false,
25 | "define": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.modernizrrc:
--------------------------------------------------------------------------------
1 | {
2 | "minify": true,
3 | "options": [
4 | "setClasses"
5 | ],
6 | "feature-detects": [
7 | "blob",
8 | "crypto",
9 | "crypto/getrandomvalues",
10 | "es6/promises",
11 | "fullscreen-api",
12 | "mathml",
13 | "notification",
14 | "storage/localstorage",
15 | "storage/sessionstorage",
16 | "storage/websqldatabase",
17 | "websockets",
18 | "websockets/binary",
19 | "workers/blobworkers",
20 | "workers/dataworkers",
21 | "workers/sharedworkers",
22 | "workers/transferables",
23 | "workers/webworkers",
24 | "indexeddb",
25 | "storage/localstorage",
26 | "storage/sessionstorage",
27 | "storage/websqldatabase"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/CONTRIBUTE.md:
--------------------------------------------------------------------------------
1 | Contribution
2 | ================
3 | Note, all contributions should be done on `dev` branch.
4 |
5 |
6 | ### Localizations
7 | ----------------
8 | 1. Copy ./app/locales/en and rename it to locale name - ./app/locales/[localeName]
9 | 2. Open ./app/locales/[localeName]/translation.json
10 | 3. Replace the values
11 | 4. Make a *pull request*
12 |
--------------------------------------------------------------------------------
/app/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | Laverna
5 |
6 | Open source note taking application
7 |
8 |
9 | Laverna project
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/docs/howto.md:
--------------------------------------------------------------------------------
1 | ### How to use tags in Laverna
2 |
3 | When you are editing or creating notes, write "@" before any word to create a tag.
4 |
5 | ### How to use tasks in Laverna
6 |
7 | You can create tasks by prefacing line with [ ] or [x] (incomplete or complete, respectively). Tasks will be automatically rendered as checkboxes that you can check on and off.
8 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/favicon.ico
--------------------------------------------------------------------------------
/app/images/icon/IconMenubarTemplate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/IconMenubarTemplate.png
--------------------------------------------------------------------------------
/app/images/icon/IconMenubarTemplate@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/IconMenubarTemplate@2x.png
--------------------------------------------------------------------------------
/app/images/icon/icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-120x120.png
--------------------------------------------------------------------------------
/app/images/icon/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-128x128.png
--------------------------------------------------------------------------------
/app/images/icon/icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-150x150.png
--------------------------------------------------------------------------------
/app/images/icon/icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-152x152.png
--------------------------------------------------------------------------------
/app/images/icon/icon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-16x16.png
--------------------------------------------------------------------------------
/app/images/icon/icon-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-310x150.png
--------------------------------------------------------------------------------
/app/images/icon/icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-310x310.png
--------------------------------------------------------------------------------
/app/images/icon/icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-36x36.png
--------------------------------------------------------------------------------
/app/images/icon/icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-48x48.png
--------------------------------------------------------------------------------
/app/images/icon/icon-512x512.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-512x512.icns
--------------------------------------------------------------------------------
/app/images/icon/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-512x512.png
--------------------------------------------------------------------------------
/app/images/icon/icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-70x70.png
--------------------------------------------------------------------------------
/app/images/icon/icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-72x72.png
--------------------------------------------------------------------------------
/app/images/icon/icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-76x76.png
--------------------------------------------------------------------------------
/app/images/icon/icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon-96x96.png
--------------------------------------------------------------------------------
/app/images/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/icon/icon.png
--------------------------------------------------------------------------------
/app/images/laverna.icon.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/images/laverna.icon.xcf
--------------------------------------------------------------------------------
/app/manifest.webapp:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laverna",
3 | "description": "Open source note taking web application",
4 | "version": "0.6.2",
5 | "launch_path": "/index.html",
6 | "icons": {
7 | "512": "/images/icon/icon.png",
8 | "128": "/images/icon/icon-128x128.png",
9 | "16": "/images/icon/icon-16x16.png",
10 | "36": "/images/icon/icon-36x36.png",
11 | "48": "/images/icon/icon-48x48.png",
12 | "72": "/images/icon/icon-72x72.png"
13 | },
14 | "developer": {
15 | "name": "Laverna project",
16 | "url": "https://github.com/Laverna/laverna/"
17 | },
18 | "default_locale": "en",
19 | "installs_allowed_from": ["*"]
20 | }
21 |
--------------------------------------------------------------------------------
/app/robots.txt:
--------------------------------------------------------------------------------
1 | # robotstxt.org
2 |
3 | User-agent: *
4 |
--------------------------------------------------------------------------------
/app/scripts/behaviors/ModalForm.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module behavior/ModalForm
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * Modal form behavior.
9 | *
10 | * @class
11 | * @extends Marionette.Behavior
12 | * @license MPL-2.0
13 | */
14 | export default class ModalForm extends Mn.Behavior {
15 |
16 | get uiFocus() {
17 | return this.view.uiFocus || 'name';
18 | }
19 |
20 | triggers() {
21 | return {
22 | 'submit form' : 'save',
23 | 'click .ok' : 'save',
24 | 'click .cancelBtn' : 'cancel',
25 | };
26 | }
27 |
28 | modelEvents() {
29 | return {
30 | invalid: 'showErrors',
31 | };
32 | }
33 |
34 | constructor(...args) {
35 | super(...args);
36 |
37 | this.events = {
38 | [`keyup @ui.${this.uiFocus}`]: 'closeOnEsc',
39 | };
40 | }
41 |
42 | /**
43 | * Focus on an ui element.
44 | */
45 | onShownModal() {
46 | this.view.ui[this.uiFocus].focus();
47 | }
48 |
49 | /**
50 | * Trigger "cancel" event if user hits Escape.
51 | */
52 | closeOnEsc(e) {
53 | if (e.which === 27) {
54 | this.view.trigger('cancel');
55 | }
56 | }
57 |
58 | /**
59 | * Indicate that validation errors occured by highlighting
60 | * form elements with red color.
61 | *
62 | * @param {Object} data
63 | * @param {Object} data.errors
64 | */
65 | showErrors(data) {
66 | _.each(data.errors, err => {
67 | this.view.ui[err].parent().addClass('has-error');
68 | });
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/app/scripts/behaviors/ModelFocus.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module behaviors/ModelFocus
3 | */
4 | import Mn from 'backbone.marionette';
5 | import $ from 'jquery';
6 |
7 | /**
8 | * Handle navigation between models.
9 | *
10 | * @class
11 | * @extends Marionette.Behavior
12 | * @license MPL-2.0
13 | */
14 | export default class ModelFocus extends Mn.Behavior {
15 |
16 | ui() {
17 | return {
18 | listGroup: '.list-group-item:first',
19 | };
20 | }
21 |
22 | modelEvents() {
23 | return {
24 | focus: 'onFocus',
25 | };
26 | }
27 |
28 | /**
29 | * The model is active (under focus).
30 | */
31 | onFocus() {
32 | $('.list-group-item.active').removeClass('active');
33 | this.ui.listGroup.addClass('active');
34 | this.view.trigger('scroll:top', {offset: this.ui.listGroup.offset()});
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/app/scripts/behaviors/Sidebar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module behaviors/Sidebar
3 | */
4 | import Mn from 'backbone.marionette';
5 | import $ from 'jquery';
6 | import Hammer from 'hammerjs';
7 | import Radio from 'backbone.radio';
8 |
9 | /**
10 | * Sidebar region behavior.
11 | *
12 | * @class
13 | * @extends Marionette.Behavior
14 | * @license MPL-2.0
15 | */
16 | export default class Sidebar extends Mn.Behavior {
17 |
18 | /**
19 | * Stop listening to touch events.
20 | */
21 | onDestroy() {
22 | if (this.hammer) {
23 | this.hammer.destroy();
24 | }
25 | }
26 |
27 | /**
28 | * Start listening to touch events.
29 | */
30 | onRender() {
31 | this.hammer = new Hammer($('#sidebar--content')[0]);
32 |
33 | this.hammer.on('swiperight', () => this.onSwipeRight());
34 | this.hammer.on('swipeleft', () => this.onSwipeLeft());
35 | }
36 |
37 | /**
38 | * Show sidebar menu on swiperight.
39 | */
40 | onSwipeRight() {
41 | Radio.trigger('components/navbar', 'show:sidemenu');
42 | }
43 |
44 | /**
45 | * Switch to content region (hide sidebar) on swipeleft.
46 | */
47 | onSwipeLeft() {
48 | // Notebooks component does not use content region
49 | if (!this.view.noSwipeLeft) {
50 | Radio.request('Layout', 'toggleContent', {visible: true});
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/app/scripts/collections/DiffCollection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module collections/DiffCollection
3 | */
4 | import _ from 'underscore';
5 | import Collection from './Collection';
6 |
7 | /**
8 | * Core collection for diffsync.
9 | *
10 | * @class
11 | * @extends Marionette.Object
12 | * @license MPL-2.0
13 | */
14 | export default class DiffCollection extends Collection {
15 |
16 | /**
17 | * Find a shadow/edit for a particular document.
18 | *
19 | * @param {Object} peer - peer information (username, deviceId)
20 | * @param {Object} doc - document model
21 | */
22 | findForDoc(peer, doc) {
23 | return this.findOrCreate({
24 | username : peer.username,
25 | deviceId : peer.deviceId,
26 | docId : doc.id,
27 | docType : doc.storeName,
28 | });
29 | }
30 |
31 | /**
32 | * Find a model or create a new one.
33 | *
34 | * @param {Object} data
35 | * @param {String} data.docId
36 | * @param {String} data.docType
37 | * @param {String} data.username
38 | * @param {String} data.deviceId
39 | * @returns {Object}
40 | */
41 | findOrCreate(data) {
42 | const mData = _.pick(data, 'docId', 'docType', 'username', 'deviceId');
43 |
44 | let model = this.findWhere(mData);
45 | if (model) {
46 | return model;
47 | }
48 |
49 | model = new this.model(mData);
50 | this.add(model);
51 | return model;
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/app/scripts/collections/Edits.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module collections/Edits
3 | */
4 | import DiffCollection from './DiffCollection';
5 | import Edit from '../models/Edit';
6 |
7 | /**
8 | * Edits collection.
9 | *
10 | * @class
11 | * @extends module:collections/DiffCollection
12 | * @license MPL-2.0
13 | */
14 | export default class Edits extends DiffCollection {
15 |
16 | /**
17 | * Edit model.
18 | *
19 | * @see module:models/Edit
20 | * @prop {Object}
21 | */
22 | get model() {
23 | return Edit;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/app/scripts/collections/Files.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module collections/Files
3 | */
4 | import File from '../models/File';
5 | import Collection from './Collection';
6 |
7 | /**
8 | * File collection.
9 | *
10 | * @class
11 | * @extends module:collections/Collection
12 | * @license MPL-2.0
13 | */
14 | export default class Files extends Collection {
15 |
16 | /**
17 | * File model.
18 | *
19 | * @returns {Object}
20 | */
21 | get model() {
22 | return File;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/scripts/collections/Profiles.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module collections/Profiles
3 | */
4 | import Collection from './Collection';
5 | import Profile from '../models/Profile';
6 |
7 | /**
8 | * Profiles collection.
9 | *
10 | * @class
11 | * @extends module:collections/Collection
12 | * @license MPL-2.0
13 | */
14 | export default class Profiles extends Collection {
15 |
16 | /**
17 | * Profile model.
18 | *
19 | * @returns {Object}
20 | */
21 | get model() {
22 | return Profile;
23 | }
24 |
25 | destroyUser(opt) {
26 | console.log('destroyUser(): calling destroyDb()');
27 | this.sync('destroyDb', opt, {});
28 | }
29 |
30 | constructor(models) {
31 | // Change the profileId to "default"
32 | super(models, {profileId: 'default'});
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/scripts/collections/Shadows.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module collections/Shadows
3 | */
4 | import DiffCollection from './DiffCollection';
5 | import Shadow from '../models/Shadow';
6 |
7 | /**
8 | * Shadows collection.
9 | *
10 | * @class
11 | * @extends module:collections/DiffCollection
12 | * @license MPL-2.0
13 | */
14 | export default class Shadows extends DiffCollection {
15 |
16 | get model() {
17 | return Shadow;
18 | }
19 |
20 | findOrCreate(...args) {
21 | const model = super.findOrCreate(...args);
22 |
23 | // Create a backup if it's a completely new model
24 | if (!model.id && !model.get('m')) {
25 | model.createBackup();
26 | }
27 |
28 | return model;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/scripts/collections/Users.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module collections/Users
3 | */
4 | import User from '../models/User';
5 | import Collection from './Collection';
6 |
7 | /**
8 | * User collection.
9 | *
10 | * @class
11 | * @extends module:collections/Collection
12 | * @license MPL-2.0
13 | */
14 | export default class Users extends Collection {
15 |
16 | /**
17 | * User model.
18 | *
19 | * @returns {Object}
20 | */
21 | get model() {
22 | return User;
23 | }
24 |
25 | /**
26 | * Return an array of users who are waiting for your approval.
27 | *
28 | * @returns {Array}
29 | */
30 | getPending() {
31 | return this.filter(model => model.get('pendingAccept'));
32 | }
33 |
34 | /**
35 | * Return an array of users whom you trust.
36 | *
37 | * @returns {Array}
38 | */
39 | getTrusted() {
40 | return this.filter(model => !model.get('pendingAccept'));
41 | }
42 |
43 | /**
44 | * Return an array of users whom you trust and who accepted your invite.
45 | * Those are the users with whom it is safe to try to establish connection.
46 | *
47 | * @returns {Array}
48 | */
49 | getActive() {
50 | return this.filter(model => {
51 | return !model.get('pendingAccept') && !model.get('pendingInvite');
52 | });
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/app/scripts/collections/modules/Edits.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module collections/modules/Edits
3 | */
4 | import Module from './Module';
5 | import Collection from '../Edits';
6 | // import _ from 'underscore';
7 | // import deb from 'debug';
8 |
9 | // const log = deb('lav:collections/modules/Edits');
10 |
11 | /**
12 | * Edits collection module.
13 | *
14 | * @class
15 | * @extends module:collections/modules/Module
16 | * @license MPL-2.0
17 | */
18 | export default class Edits extends Module {
19 |
20 | /**
21 | * Shadow collection.
22 | *
23 | * @see module:collections/Edits
24 | * @returns {Object}
25 | */
26 | get Collection() {
27 | return Collection;
28 | }
29 |
30 | constructor() {
31 | super();
32 |
33 | this.channel.reply({
34 | }, this);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/app/scripts/collections/modules/Shadows.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module collections/modules/Shadows
3 | */
4 | import Module from './Module';
5 | import Collection from '../Shadows';
6 |
7 | /**
8 | * Shadows collection module.
9 | *
10 | * @class
11 | * @extends module:collections/modules/Module
12 | * @license MPL-2.0
13 | */
14 | export default class Shadows extends Module {
15 |
16 | /**
17 | * Shadow collection.
18 | *
19 | * @see module:collections/Shadows
20 | * @returns {Object}
21 | */
22 | get Collection() {
23 | return Collection;
24 | }
25 |
26 | constructor() {
27 | super();
28 |
29 | this.channel.reply({
30 | }, this);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/app/scripts/collections/modules/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file instantiate all collection modules
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 |
7 | import Profiles from './Profiles';
8 | import Configs from './Configs';
9 | import Users from './Users';
10 | import Files from './Files';
11 | import Notebooks from './Notebooks';
12 | import Notes from './Notes';
13 | import Tags from './Tags';
14 | import Shadows from './Shadows';
15 | import Edits from './Edits';
16 |
17 | /**
18 | * Instantiate all collection modules
19 | *
20 | * @returns {Promise}
21 | */
22 | function initializer() {
23 | // Instantiate all collection modules to start listening to requests
24 | new Profiles();
25 | new Configs();
26 | new Users();
27 | new Files();
28 | new Notebooks();
29 | new Notes();
30 | new Tags();
31 | new Shadows();
32 | new Edits();
33 |
34 | // Find or create configs
35 | return Radio.request('collections/Profiles', 'find');
36 | }
37 |
38 | // Add a new initializer
39 | Radio.once('App', 'init', () => {
40 | Radio.request('utils/Initializer', 'add', {
41 | name : 'App:core',
42 | callback: initializer,
43 | });
44 | });
45 |
46 | export default initializer;
47 |
--------------------------------------------------------------------------------
/app/scripts/components/codemirror/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/codemirror/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 | import Controller from './Controller';
7 |
8 | Radio.once('App', 'init', () => {
9 | Radio.on('components/notes/form', 'ready', opt => new Controller(opt).init());
10 | });
11 |
--------------------------------------------------------------------------------
/app/scripts/components/confirm/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
{= _.cleanXSS(content) }
9 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/scripts/components/dropbox/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/dropbox/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 | import Sync from './Sync';
7 | import View from './settings/View';
8 |
9 | export default function initialize() {
10 | const sync = Radio.request('collections/Configs', 'findConfig', {
11 | name: 'cloudStorage',
12 | });
13 |
14 | // Reply with the settings view
15 | Radio.channel('components/dropbox').reply({getSettingsView: () => View});
16 |
17 | if (sync === 'dropbox') {
18 | return new Sync().init();
19 | }
20 | }
21 |
22 | Radio.once('App', 'start', initialize);
23 |
--------------------------------------------------------------------------------
/app/scripts/components/dropbox/settings/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/dropbox/show/settings/View
3 | */
4 | import _ from 'underscore';
5 | import Mn from 'backbone.marionette';
6 | import constants from '../../../constants';
7 |
8 | /**
9 | * Show a list of users whom you trust or invited.
10 | *
11 | * @class
12 | * @extends Marionette.View
13 | * @license MPL-2.0
14 | */
15 | export default class Users extends Mn.View {
16 |
17 | get template() {
18 | const tmpl = require('./template.html');
19 | return _.template(tmpl);
20 | }
21 |
22 | get dropboxKeyNeed() {
23 | return constants.dropboxKeyNeed;
24 | }
25 |
26 | serializeData() {
27 | return {
28 | dropboxKey : this.collection.get('dropboxKey').get('value'),
29 | placeholder : this.dropboxKeyNeed ? 'Required' : 'Optional',
30 | };
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/app/scripts/components/dropbox/settings/template.html:
--------------------------------------------------------------------------------
1 |
2 |
{{_.i18n('Dropbox API key')}}
3 |
4 |
5 |
6 |
14 |
--------------------------------------------------------------------------------
/app/scripts/components/electronSearch/template.html:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/app/scripts/components/encryption/auth/template.html:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/app/scripts/components/encryption/encrypt/template.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | <% if (!encrypt) { %>
10 |
{{_.i18n('Data decryption after disabling encryption')}}
11 | <% } else { %>
12 |
{{_.i18n('Data encryption after enabling encryption')}}
13 | <% } %>
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
{{ _.i18n('Please wait') }}...
32 |
33 |
--------------------------------------------------------------------------------
/app/scripts/components/encryption/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/encryption/main
3 | */
4 | import Encryption from '../../models/Encryption';
5 | import Radio from 'backbone.radio';
6 | import Auth from './auth/Controller';
7 | import Encrypt from './encrypt/Controller';
8 |
9 | export default function initialize() {
10 | // Instantiate encryption model
11 | new Encryption();
12 |
13 | // Don't start the app until auth is successful
14 | Radio.request('utils/Initializer', 'add', {
15 | name : 'App:auth',
16 | callback: () => new Auth().init(),
17 | });
18 |
19 | // In case if a user enabled/disabled encryption, encrypt/decrypt data
20 | Radio.request('utils/Initializer', 'add', {
21 | name : 'App:auth',
22 | callback: () => new Encrypt().init(),
23 | });
24 | }
25 |
26 | Radio.once('App', 'init', initialize);
27 |
--------------------------------------------------------------------------------
/app/scripts/components/fileDialog/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/fileDialog/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 | import Controller from './Controller';
7 |
8 | export default function initialize() {
9 | const channel = Radio.channel('components/fileDialog');
10 | channel.reply('show', (...args) => new Controller(...args).init());
11 | }
12 |
13 | Radio.once('App', 'init', initialize);
14 |
--------------------------------------------------------------------------------
/app/scripts/components/fileDialog/templates/dropzone.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
![]()
5 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/scripts/components/fuzzySearch/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/fuzzySearch/main
3 | * @license MPL-2.0
4 | */
5 | import $ from 'jquery';
6 | import Radio from 'backbone.radio';
7 | import Controller from './Controller';
8 | import regionClass from './views/Region';
9 |
10 | const main = {
11 |
12 | initialize() {
13 | main.createRegion();
14 |
15 | // Instantiate fuzzy search controller on shown:search event
16 | Radio.channel('components/navbar')
17 | .on('shown:search', () => new Controller().init());
18 | },
19 |
20 | /**
21 | * Create fuzzySearch region.
22 | *
23 | * @returns {Object} region instance
24 | */
25 | createRegion() {
26 | $('#sidebar').append(
27 | ''
28 | );
29 |
30 | Radio.request('Layout', 'add', {
31 | region : 'fuzzySearch',
32 | regionOptions : {regionClass, el: '#sidebar--fuzzy'},
33 | });
34 | },
35 |
36 | };
37 |
38 | Radio.once('App', 'start', main.initialize);
39 |
40 | export default main;
41 |
--------------------------------------------------------------------------------
/app/scripts/components/fuzzySearch/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/scripts/components/fuzzySearch/views/Child.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/fuzzySearch/views/Child
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * Fuzzy search child view.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class Child extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('../template.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | get className() {
22 | return 'list-group list--group';
23 | }
24 |
25 | triggers() {
26 | return {
27 | 'click .list-group-item': 'navigate:search',
28 | };
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/scripts/components/fuzzySearch/views/Region.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/fuzzySearch/views/Region
3 | */
4 | import Mn from 'backbone.marionette';
5 | import $ from 'jquery';
6 |
7 | /**
8 | * Fuzzy search region.
9 | *
10 | * @class
11 | * @extends Marionette.Region
12 | * @license MPL-2.0
13 | */
14 | export default class Region extends Mn.Region {
15 |
16 | /**
17 | * Show the region block.
18 | */
19 | onShow() {
20 | this.$body = this.$body || $('body');
21 | this.$body.addClass('-fuzzy');
22 | this.$el.removeClass('hidden');
23 | }
24 |
25 | /**
26 | * Hide the region block.
27 | */
28 | onEmpty() {
29 | this.$el.addClass('hidden');
30 | this.$body.removeClass('-fuzzy');
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/app/scripts/components/fuzzySearch/views/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/fuzzySearch/views/View
3 | */
4 | import Mn from 'backbone.marionette';
5 | import Child from './Child';
6 |
7 | /**
8 | * Fuzzy search collection view.
9 | *
10 | * @class
11 | * @extends Marionette.CollectionView
12 | * @license MPL-2.0
13 | */
14 | export default class View extends Mn.CollectionView {
15 |
16 | get className() {
17 | return 'main notes-list';
18 | }
19 |
20 | get childViewContainer() {
21 | return '.notes-list';
22 | }
23 |
24 | /**
25 | * Child view.
26 | *
27 | * @see module:components/fuzzySearch/views/Child
28 | * @prop {Object}
29 | */
30 | get childView() {
31 | return Child;
32 | }
33 |
34 | /**
35 | * @todo
36 | */
37 | get emptyView() {
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/app/scripts/components/help/about/Controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/help/about/Controller
3 | */
4 | import Mn from 'backbone.marionette';
5 | import Radio from 'backbone.radio';
6 |
7 | import constants from '../../../constants';
8 | import View from './View';
9 |
10 | /**
11 | * Controller that shows information about the app.
12 | *
13 | * @class
14 | * @extends Marionette.Object
15 | * @license MPL-2.0
16 | */
17 | export default class Controller extends Mn.Object {
18 |
19 | init() {
20 | this.show();
21 | }
22 |
23 | /**
24 | * Render the view.
25 | */
26 | show() {
27 | this.view = new View({
28 | constants,
29 | });
30 |
31 | Radio.request('Layout', 'show', {
32 | region : 'modal',
33 | view : this.view,
34 | });
35 |
36 | // Destroy itself if the view is destroyed
37 | this.listenTo(this.view, 'destroy', this.destroy);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/app/scripts/components/help/about/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/help/about/View
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * About view.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class View extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('./template.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | get className() {
22 | return 'modal fade';
23 | }
24 |
25 | serializeData() {
26 | return this.options;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/app/scripts/components/help/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/help/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 |
7 | import About from './about/Controller';
8 | import Keybindings from './keybindings/Controller';
9 |
10 | let controller;
11 | export default controller = {
12 |
13 | /**
14 | * @returns {Promise}
15 | */
16 | init() {
17 | Radio.reply('components/help', {
18 | showAbout : this.showAbout,
19 | showKeybindings : this.showKeybindings,
20 | }, this);
21 |
22 | // Show keybinding help if "?" key is pressed
23 | Radio.on('utils/Keybindings', 'appKeyboardHelp', () => this.showKeybindings());
24 | },
25 |
26 | /**
27 | * Show information about the app.
28 | */
29 | showAbout(...args) {
30 | return new About(...args).init();
31 | },
32 |
33 | /**
34 | * Shows keybinding help.
35 | */
36 | showKeybindings(...args) {
37 | return new Keybindings(...args).init();
38 | },
39 |
40 | };
41 |
42 | Radio.once('App', 'init', () => controller.init());
43 |
--------------------------------------------------------------------------------
/app/scripts/components/help/keybindings/Controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/help/keybindings/Controller
3 | */
4 | import Mn from 'backbone.marionette';
5 | import Radio from 'backbone.radio';
6 | import View from './View';
7 |
8 | import deb from 'debug';
9 |
10 | const log = deb('lav:components/help/keybindings/Controller');
11 |
12 | /**
13 | * Controller that shows keybinding help.
14 | *
15 | * @class
16 | * @extends Marionette.Object
17 | * @license MPL-2.0
18 | */
19 | export default class Controller extends Mn.Object {
20 |
21 | /**
22 | * Fetch configs collection and render the view.
23 | *
24 | * @returns {Promise}
25 | */
26 | init() {
27 | return Radio.request('collections/Configs', 'find')
28 | .then(configs => this.show(configs))
29 | .catch(err => log('error', err));
30 | }
31 |
32 | /**
33 | * Render the view in modal region.
34 | *
35 | * @param {Object} configs - config collection
36 | */
37 | show(configs) {
38 | const collection = configs.clone();
39 | collection.reset(collection.keybindings());
40 |
41 | this.view = new View({
42 | collection,
43 | });
44 |
45 | Radio.request('Layout', 'show', {
46 | region : 'modal',
47 | view : this.view,
48 | });
49 |
50 | // Destroy itself if the view destroyed
51 | this.listenTo(this.view, 'destroy', this.destroy);
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/app/scripts/components/help/keybindings/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/help/keybindings/View
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * View that shows keybinding help.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class View extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('./template.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | get className() {
22 | return 'modal fade';
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/scripts/components/help/keybindings/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 | {{_.i18n('Action')}} |
17 | {{_.i18n('Keybindings')}} |
18 |
19 |
20 |
21 | <% _.forEach(items, function(conf) { %>
22 |
23 | {{ _.i18n(conf.name) }} |
24 | {{ conf.value }} |
25 |
26 | <% }); %>
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/scripts/components/importExport/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/importExport/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 | import Import from './Import';
7 | import ImportEvernote from './ImportEvernote';
8 | import Export from './Export';
9 | import Migrate from './migrate/Controller';
10 |
11 | function initialize() {
12 | Radio.channel('components/importExport')
13 | .reply({
14 | import : (...args) => new Import(...args).init(),
15 | importEvernote : (...args) => new ImportEvernote(...args).init(),
16 | export : (...args) => new Export(...args).init(),
17 | });
18 |
19 | Radio.request('utils/Initializer', 'add', {
20 | name : 'App:last',
21 | callback : () => new Migrate().init(),
22 | });
23 | }
24 |
25 | Radio.once('App', 'init', initialize);
26 | export default initialize;
27 |
--------------------------------------------------------------------------------
/app/scripts/components/linkDialog/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/linkDialog/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 | import Controller from './Controller';
7 |
8 | export default function initialize() {
9 | const channel = Radio.channel('components/linkDialog');
10 | channel.reply('show', () => new Controller().init());
11 | }
12 |
13 | Radio.once('App', 'init', initialize);
14 |
--------------------------------------------------------------------------------
/app/scripts/components/linkDialog/templates/item.html:
--------------------------------------------------------------------------------
1 | {{title}}
2 |
--------------------------------------------------------------------------------
/app/scripts/components/linkDialog/templates/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
19 |
20 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/scripts/components/linkDialog/views/Collection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/linkDialog/views/Collection
3 | */
4 | import Mn from 'backbone.marionette';
5 | import View from './Item';
6 | import Radio from 'backbone.radio';
7 |
8 | /**
9 | * Link dialog collection view. Shows a list of notes.
10 | *
11 | * @class
12 | * @extends Marionette.CollectionView
13 | * @license MPL-2.0
14 | */
15 | export default class Collection extends Mn.CollectionView {
16 |
17 | get tagName() {
18 | return 'ul';
19 | }
20 |
21 | get className() {
22 | return 'dropdown-menu';
23 | }
24 |
25 | get childViewContainer() {
26 | return '.dropdown-menu';
27 | }
28 |
29 | /**
30 | * Child view.
31 | *
32 | * @see module:components/linkDialog/views/Item
33 | * @prop {Object}
34 | */
35 | get childView() {
36 | return View;
37 | }
38 |
39 | events() {
40 | return {
41 | 'click a': 'triggerAttach',
42 | };
43 | }
44 |
45 | /**
46 | * Trigger attach:link event.
47 | *
48 | * @param {Object} e
49 | */
50 | triggerAttach(e) {
51 | const id = this.$(e.currentTarget).attr('data-id');
52 | const url = Radio.request('utils/Url', 'getNoteLink', {id});
53 | this.trigger('attach:link', {url: `#${url}`});
54 | return false;
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/app/scripts/components/linkDialog/views/Item.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/linkDialog/views/Item
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * Link dialog item view.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class Item extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('../templates/item.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | get tagName() {
22 | return 'li';
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/scripts/components/markdown/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/markdown/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 | import Markdown from './Markdown';
7 |
8 | Radio.once('App', 'init', () => new Markdown());
9 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/Router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notebooks/Router
3 | */
4 | import Mn from 'backbone.marionette';
5 | import Radio from 'backbone.radio';
6 |
7 | import controller from './controller';
8 |
9 | /**
10 | * Notebooks router.
11 | *
12 | * @class
13 | * @extends Marionette.AppRouter
14 | * @license MPL-2.0
15 | */
16 | export default class Router extends Mn.AppRouter {
17 |
18 | /**
19 | * Controller.
20 | *
21 | * @see module:components/Notebooks/controller
22 | * @returns {Object}
23 | */
24 | get controller() {
25 | return controller;
26 | }
27 |
28 | /**
29 | * appRoutes.
30 | *
31 | * @returns {Object}
32 | */
33 | get appRoutes() {
34 | return {
35 | notebooks : 'showList',
36 | 'notebooks/add' : 'notebookForm',
37 | 'notebooks/edit/:id' : 'notebookForm',
38 | 'tags/add' : 'tagForm',
39 | 'tags/edit/:id' : 'tagForm',
40 | };
41 | }
42 |
43 | }
44 |
45 | Radio.once('App', 'init', () => {
46 | // Instantiate the router
47 | new Router();
48 |
49 | // Start replying to "notebookForm" request
50 | Radio.reply('components/notebooks', 'notebookForm', opt => {
51 | return controller.notebookFormReply(opt);
52 | });
53 |
54 | // Start replying to "tagForm" request
55 | Radio.reply('components/notebooks', 'tagForm', opt => {
56 | return controller.tagFormReply(opt);
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/form/notebook/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notebooks/form/notebook/View
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 | import ModalForm from '../../../../behaviors/ModalForm';
7 |
8 | /**
9 | * Notebook form view.
10 | *
11 | * @class
12 | * @extends Marionette.View
13 | * @license MPL-2.0
14 | */
15 | export default class View extends Mn.View {
16 |
17 | get template() {
18 | const tmpl = require('./template.html');
19 | return _.template(tmpl);
20 | }
21 |
22 | get className() {
23 | return 'modal fade';
24 | }
25 |
26 | ui() {
27 | return {
28 | name : 'input[name="name"]',
29 | parentId : 'select[name="parentId"]',
30 | };
31 | }
32 |
33 | /**
34 | * Behaviors.
35 | *
36 | * @see module:behavior/ModalForm
37 | * @returns {Array}
38 | */
39 | behaviors() {
40 | return [ModalForm];
41 | }
42 |
43 | serializeData() {
44 | return _.extend({}, this.model.attributes, {
45 | notebooks: this.options.notebooks.toJSON(),
46 | });
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/form/tag/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notebooks/form/tag/View
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 | import ModalForm from '../../../../behaviors/ModalForm';
7 |
8 | /**
9 | * Tag form view.
10 | *
11 | * @class
12 | * @extends Marionette.View
13 | * @license MPL-2.0
14 | */
15 | export default class View extends Mn.View {
16 |
17 | get template() {
18 | const tmpl = require('./template.html');
19 | return _.template(tmpl);
20 | }
21 |
22 | get className() {
23 | return 'modal fade';
24 | }
25 |
26 | ui() {
27 | return {
28 | name: 'input[name="name"]',
29 | };
30 | }
31 |
32 | /**
33 | * Behaviors.
34 | *
35 | * @see module:behavior/ModalForm
36 | * @returns {Array}
37 | */
38 | behaviors() {
39 | return [ModalForm];
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/form/tag/template.html:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/list/templates/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ _.i18n('Tags') }}
6 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/list/templates/notebook.html:
--------------------------------------------------------------------------------
1 |
3 |
4 | {=_.cleanXSS(name)}
5 |
6 |
7 |
8 |
11 |
15 |
16 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/list/templates/tag.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {=_.cleanXSS(name)}
4 |
5 |
6 |
7 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/list/views/ItemView.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notebooks/list/views/ItemView
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 | import Radio from 'backbone.radio';
7 | import ModelFocus from '../../../../behaviors/ModelFocus';
8 |
9 | /**
10 | * Item view. Views like Notebook.js and Tag.js extend from this.
11 | *
12 | * @class
13 | * @extends Marionette.View
14 | * @license MPL-2.0
15 | */
16 | export default class ItemView extends Mn.View {
17 |
18 | get className() {
19 | return 'list--group list-group';
20 | }
21 |
22 | events() {
23 | return {
24 | 'click .remove-link': 'removeModel',
25 | };
26 | }
27 |
28 | /**
29 | * Behaviors.
30 | *
31 | * @see module:behaviors/ModelFocus
32 | * @returns {Array}
33 | */
34 | behaviors() {
35 | return [ModelFocus];
36 | }
37 |
38 | /**
39 | * Return options and model attributes.
40 | *
41 | * @returns {Object}
42 | */
43 | serializeData() {
44 | return _.extend({}, this.options, this.model.attributes);
45 | }
46 |
47 | /**
48 | * Remove a model.
49 | */
50 | removeModel() {
51 | Radio.request('components/notebooks', 'remove', {model: this.model});
52 | return false;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/list/views/Notebook.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notebooks/list/views/Notebook
3 | */
4 | import _ from 'underscore';
5 | import ItemView from './ItemView';
6 |
7 | /**
8 | * Notebook list item view.
9 | *
10 | * @class
11 | * @extends module:components/notebooks/list/views/ItemView
12 | * @license MPL-2.0
13 | */
14 | export default class Notebook extends ItemView {
15 |
16 | get template() {
17 | const tmpl = require('../templates/notebook.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | modelEvents() {
22 | return {
23 | change: 'render',
24 | };
25 | }
26 |
27 | templateContext() {
28 | return {
29 | getPadding() {
30 | if (this.level === 1) {
31 | return '';
32 | }
33 |
34 | return `padding-left:${this.level * 20}px`;
35 | },
36 | };
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/list/views/Notebooks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notebooks/list/views/Notebooks
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 | import deb from 'debug';
7 | import Notebook from './Notebook';
8 | import Navigate from '../../../../behaviors/Navigate';
9 |
10 | const log = deb('lav:components/notebooks/list/views/Notebooks');
11 |
12 | /**
13 | * Notebooks collection view.
14 | *
15 | * @class
16 | * @extends Marionette.CollectionView
17 | * @license MPL-2.0
18 | */
19 | export default class Notebooks extends Mn.CollectionView {
20 |
21 | get className() {
22 | return 'list--notebooks';
23 | }
24 |
25 | /**
26 | * Behaviors.
27 | *
28 | * @see module:behaviors/Navigate
29 | * @returns {Array}
30 | */
31 | behaviors() {
32 | return [Navigate];
33 | }
34 |
35 | /**
36 | * Child view.
37 | *
38 | * @see module:components/notebooks/list/views/Notebook
39 | * @returns {Object}
40 | */
41 | childView() {
42 | return Notebook;
43 | }
44 |
45 | /**
46 | * Child view options.
47 | *
48 | * @returns {Object}
49 | */
50 | childViewOptions() {
51 | return _.omit(this.options, 'collection');
52 | }
53 |
54 | initialize() {
55 | log('init');
56 | this.options.filterArgs = {};
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/list/views/Tag.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notebooks/list/views/Tag
3 | */
4 | import _ from 'underscore';
5 | import ItemView from './ItemView';
6 |
7 | /**
8 | * Tag list item view.
9 | *
10 | * @class
11 | * @extends module:components/notebooks/list/views/ItemView
12 | * @license MPL-2.0
13 | */
14 | export default class Tag extends ItemView {
15 |
16 | get template() {
17 | const tmpl = require('../templates/tag.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/scripts/components/notebooks/list/views/Tags.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notebooks/list/views/Tags
3 | */
4 | import Mn from 'backbone.marionette';
5 | import Tag from './Tag';
6 | import _ from 'underscore';
7 | import Navigate from '../../../../behaviors/Navigate';
8 |
9 | /**
10 | * Tags collection view.
11 | *
12 | * @class
13 | * @extends Marionette.CollectionView
14 | * @license MPL-2.0
15 | */
16 | export default class Tags extends Mn.CollectionView {
17 |
18 | get className() {
19 | return 'list list--tags';
20 | }
21 |
22 | /**
23 | * Behaviors.
24 | *
25 | * @see module:behaviors/Navigate
26 | * @returns {Array}
27 | */
28 | behaviors() {
29 | return [Navigate];
30 | }
31 |
32 | /**
33 | * Child view.
34 | *
35 | * @see module:components/notebooks/list/views/Tag
36 | * @returns {Object}
37 | */
38 | childView() {
39 | return Tag;
40 | }
41 |
42 | /**
43 | * Child view options.
44 | *
45 | * @returns {Object}
46 | */
47 | childViewOptions() {
48 | return _.omit(this.options, 'collection');
49 | }
50 |
51 | initialize() {
52 | this.options.filterArgs = {};
53 |
54 | // Collection channel events
55 | this.listenTo(this.collection.channel, 'page:next', this.getNextPage);
56 | }
57 |
58 | /**
59 | * Show models from the next page.
60 | */
61 | getNextPage() {
62 | this.collection.pagination.current += 1;
63 | this.collection.getNextPage();
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/scripts/components/notes/Router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/Notes/Router
3 | */
4 | import Mn from 'backbone.marionette';
5 | import Radio from 'backbone.radio';
6 |
7 | import controller from './controller';
8 |
9 | /**
10 | * Notes router.
11 | *
12 | * @class
13 | * @extends Marionette.AppRouter
14 | * @license MPL-2.0
15 | */
16 | export default class Router extends Mn.AppRouter {
17 |
18 | /**
19 | * Controller.
20 | *
21 | * @see module:components/Notes/controller
22 | * @returns {Object}
23 | */
24 | get controller() {
25 | return controller;
26 | }
27 |
28 | /**
29 | * appRoutes.
30 | *
31 | * @returns {Object}
32 | */
33 | get appRoutes() {
34 | const filter = 'notes(/f/:filter)(/q/:query)(/p:page)';
35 |
36 | return {
37 | '' : 'showNotes',
38 |
39 | // Show notes list
40 | [`${filter}`] : 'showNotes',
41 | [`${filter}/show/:id`] : 'showNote',
42 |
43 | // Edit/add notes
44 | 'notes/add' : 'showForm',
45 | 'notes/edit/:id' : 'showForm',
46 | };
47 | }
48 |
49 | }
50 |
51 | // Instantiate the router automatically
52 | Radio.once('App', 'init', () => new Router());
53 |
--------------------------------------------------------------------------------
/app/scripts/components/notes/form/templates/notebooks.html:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
--------------------------------------------------------------------------------
/app/scripts/components/notes/form/views/Notebook.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notes/form/views/Notebook
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * Notebook item view.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class Notebook extends Mn.View {
15 |
16 | get template() {
17 | return _.template('{=_.cleanXSS(name)}');
18 | }
19 |
20 | get tagName() {
21 | return 'option';
22 | }
23 |
24 | onRender() {
25 | this.$el.attr('value', this.model.get('id'));
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/scripts/components/notes/form/views/NotebooksCollection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notes/form/views/NotebooksCollection
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 | import Notebook from './Notebook';
7 |
8 | /**
9 | * Notebooks collection view.
10 | *
11 | * @class
12 | * @extends Marionette.CollectionView
13 | * @license MPL-2.0
14 | */
15 | export default class NotebooksCollection extends Mn.CollectionView {
16 |
17 | get tagName() {
18 | return 'optgroup';
19 | }
20 |
21 | get className() {
22 | return 'editor--notebooks--select';
23 | }
24 |
25 | /**
26 | * Child view.
27 | *
28 | * @see modules:components/notes/form/views/Notebook
29 | * @returns {Object}
30 | */
31 | childView() {
32 | return Notebook;
33 | }
34 |
35 | onRender() {
36 | this.$el.attr('label', _.i18n('Notebooks'));
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/scripts/components/notes/list/templates/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <% if (collection.pagination.total > 0) { %>
5 |
21 | <% } %>
22 |
--------------------------------------------------------------------------------
/app/scripts/components/notes/list/templates/noteView.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {= _.cleanXSS(title, false, true) }
4 |
5 |
6 |
7 |
8 |
9 | <% _(getTags()).each(function(t) { %>
10 | {{t}}
11 | <% }) %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/scripts/components/notes/list/views/Layout.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notes/list/views/Layout
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 | import deb from 'debug';
7 |
8 | import NotesView from './NotesView';
9 | import Pagination from '../../../../behaviors/Pagination';
10 | import Sidebar from '../../../../behaviors/Sidebar';
11 |
12 | const log = deb('lav:components/notes/list/views/Layout');
13 |
14 | /**
15 | * Sidebar layout that shows a list of notes.
16 | *
17 | * @class
18 | * @extends Marionette.View
19 | * @license MPL-2.0
20 | */
21 | export default class Layout extends Mn.View {
22 |
23 | get template() {
24 | const tmpl = require('../templates/layout.html');
25 | return _.template(tmpl);
26 | }
27 |
28 | /**
29 | * Behaviors.
30 | *
31 | * @see module:behaviors/Pagination
32 | * @see module:behaviors/Sidebar
33 | * @returns {Array}
34 | */
35 | behaviors() {
36 | return [Sidebar, Pagination];
37 | }
38 |
39 | /**
40 | * Regions.
41 | *
42 | * @returns {Object}
43 | */
44 | regions() {
45 | return {
46 | notes: '.list',
47 | };
48 | }
49 |
50 | /**
51 | * Show notes collection view.
52 | */
53 | onRender() {
54 | log('rendering notes collection view', this.options);
55 | this.showChildView('notes', new NotesView(this.options));
56 | }
57 |
58 | templateContext() {
59 | return {
60 | collection: this.collection,
61 | };
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/scripts/components/notes/list/views/NotesView.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/notes/list/views/NotesView
3 | */
4 | import Mn from 'backbone.marionette';
5 | import Radio from 'backbone.radio';
6 | import NoteView from './NoteView';
7 | import Navigate from '../../../../behaviors/Navigate';
8 |
9 | /**
10 | * Notes collection view.
11 | *
12 | * @class
13 | * @extends Marionette.CollectionView
14 | * @license MPL-2.0
15 | */
16 | export default class NotesView extends Mn.CollectionView {
17 |
18 | /**
19 | * Listen to keybindings used for navigating between items (j-k).
20 | *
21 | * @see module:behaviors/Navigate
22 | * @returns {Boolean} true
23 | */
24 | get useNavigateKeybindings() {
25 | return true;
26 | }
27 |
28 | /**
29 | * Component Radio channel.
30 | *
31 | * @prop {Object}
32 | */
33 | get channel() {
34 | return Radio.channel('components/notes');
35 | }
36 |
37 | /**
38 | * Behaviors.
39 | *
40 | * @see module:behaviors/Navigate
41 | * @returns {Array}
42 | */
43 | behaviors() {
44 | return [Navigate];
45 | }
46 |
47 | /**
48 | * Child view.
49 | *
50 | * @see components/notes/list/views/NoteView
51 | * @returns {Object}
52 | */
53 | childView() {
54 | return NoteView;
55 | }
56 |
57 | /**
58 | * Child view options.
59 | *
60 | * @returns {Object}
61 | */
62 | childViewOptions() {
63 | return {filterArgs: this.options.filterArgs};
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/Router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/settings/Router
3 | */
4 | import Mn from 'backbone.marionette';
5 | import Radio from 'backbone.radio';
6 | import controller from './controller';
7 |
8 | /**
9 | * Settings router.
10 | *
11 | * @class
12 | * @extends Marionette.AppRouter
13 | * @license MPL-2.0
14 | */
15 | export default class Router extends Mn.AppRouter {
16 |
17 | /**
18 | * Controller.
19 | *
20 | * @see module:components/settings/controller
21 | * @prop {Object}
22 | */
23 | get controller() {
24 | return controller;
25 | }
26 |
27 | /**
28 | * appRoutes
29 | *
30 | * @prop {Object}
31 | */
32 | get appRoutes() {
33 | return {
34 | 'settings(/:tab)' : 'showContent',
35 | };
36 | }
37 |
38 | }
39 |
40 | // Instantiate the router automatically
41 | Radio.once('App', 'init', () => new Router());
42 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/show/editor/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/settings/show/editor/View
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 | import Behavior from '../Behavior';
7 |
8 | /**
9 | * Editor settings view.
10 | *
11 | * @class
12 | * @extends Marionette.View
13 | * @license MPL-2.0
14 | */
15 | export default class View extends Mn.View {
16 |
17 | get template() {
18 | const tmpl = require('./template.html');
19 | return _.template(tmpl);
20 | }
21 |
22 | /**
23 | * Behaviors.
24 | *
25 | * @see module:components/settings/show/Behavior
26 | * @returns {Array}
27 | */
28 | get behaviors() {
29 | return [Behavior];
30 | }
31 |
32 | ui() {
33 | return {
34 | indentUnit : '#indentUnit',
35 | indentWarning : '#indentUnit-low-warning',
36 | };
37 | }
38 |
39 | events() {
40 | return {
41 | 'change @ui.indentUnit' : 'checkIndentUnit',
42 | };
43 | }
44 |
45 | /**
46 | * Show indentation warning if its value is lower than 3.
47 | */
48 | checkIndentUnit() {
49 | const indent = Number(this.ui.indentUnit.val().trim());
50 | this.ui.indentWarning.toggleClass('hidden', indent >= 3);
51 | }
52 |
53 | serializeData() {
54 | return {
55 | models: this.collection.getConfigs(),
56 | };
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/show/encryption/template.html:
--------------------------------------------------------------------------------
1 |
9 |
10 | <% if (privateKey) { %>
11 |
12 |
13 | {{_.i18n('Your key')}}
14 |
15 |
16 |
20 |
24 |
25 | <% } %>
26 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/show/importExport/template.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
23 |
24 |
25 |
34 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/show/keybindings/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/settings/show/keybindings/View
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 | import Behavior from '../Behavior';
7 |
8 | /**
9 | * Keybindings settings view.
10 | *
11 | * @class
12 | * @extends Marionette.View
13 | * @license MPL-2.0
14 | */
15 | export default class View extends Mn.View {
16 |
17 | get template() {
18 | const tmpl = require('./template.html');
19 | return _.template(tmpl);
20 | }
21 |
22 | /**
23 | * Behaviors.
24 | *
25 | * @see module:components/settings/show/Behavior
26 | * @returns {Array}
27 | */
28 | get behaviors() {
29 | return [Behavior];
30 | }
31 |
32 | serializeData() {
33 | return this.options;
34 | }
35 |
36 | templateContext() {
37 | return {
38 | /**
39 | * Return an array of models filtered by their names.
40 | *
41 | * @param {String} str
42 | * @returns {Array}
43 | */
44 | filter(str) {
45 | return this.collection.filterByName(str);
46 | },
47 |
48 | /**
49 | * Application wide shortcuts.
50 | *
51 | * @returns {Array}
52 | */
53 | appShortcuts() {
54 | return this.collection.appShortcuts();
55 | },
56 | };
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/show/sync/template.html:
--------------------------------------------------------------------------------
1 |
2 |
{{_.i18n('Synchronization method')}}
3 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/show/template.html:
--------------------------------------------------------------------------------
1 |
20 |
21 |
26 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/show/wipe/confirm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
{{_.i18n('This will delete all data for this username on this computer')}}!
10 |
11 |
12 |
13 |
14 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/show/wipe/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{_.i18n('Erasure options')}}
4 |
5 |
6 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/sidebar/templates/navbar.html:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/sidebar/templates/template.html:
--------------------------------------------------------------------------------
1 |
35 |
--------------------------------------------------------------------------------
/app/scripts/components/settings/sidebar/views/Navbar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/settings/sidebar/Navbar
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * Settings navbar view for the sidebar.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class Navbar extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('../templates/navbar.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/scripts/components/setup/export/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/setup/export/View
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * Export the generated key.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class Export extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('./template.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | ui() {
22 | return {
23 | sync: '[name=sync]',
24 | };
25 | }
26 |
27 | className() {
28 | return 'row';
29 | }
30 |
31 | serializeData() {
32 | return this.options;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/scripts/components/setup/export/template.html:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/app/scripts/components/setup/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/setup/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 | import Controller from './Controller';
7 |
8 | export default function initialize() {
9 | // On "start" request show the "setup" form
10 | Radio.reply('components/setup', 'start', opt => new Controller(opt).init());
11 |
12 | // Add a new initializer to show the "setup" form on start
13 | Radio.request('utils/Initializer', 'add', {
14 | name : 'App:components',
15 | callback: () => new Controller().init(),
16 | });
17 | }
18 |
19 | Radio.once('App', 'init', initialize);
20 |
--------------------------------------------------------------------------------
/app/scripts/components/setup/template.html:
--------------------------------------------------------------------------------
1 |
2 | {{_.i18n('Laverna')}}
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/scripts/components/share/info/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/share/Info
3 | */
4 | import _ from 'underscore';
5 | import Mn from 'backbone.marionette';
6 |
7 | /**
8 | * Show information about a user.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class Info extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('./template.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | triggers() {
22 | return {
23 | 'click .share--trust': 'add:trust',
24 | };
25 | }
26 |
27 | serializeData() {
28 | return this.options;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/scripts/components/share/info/template.html:
--------------------------------------------------------------------------------
1 |
2 | Before adding a user to trust, check their key fingerprint.
3 | You can ask the user to share their fingerprint by email or phone.
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
{{user.username}}
15 |
{{_.i18n('Key fingerprint')}}: {{_.splitBy4(user.fingerprint)}}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/scripts/components/share/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
20 |
21 |
{{_.i18n('User does not exist')}}
22 |
23 |
24 |
25 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/scripts/components/share/users/View.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module components/share/Users
3 | */
4 | import _ from 'underscore';
5 | import Mn from 'backbone.marionette';
6 |
7 | /**
8 | * Show trusted users.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class Users extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('./template.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | events() {
22 | return {
23 | 'click .share--toggle' : 'share',
24 | };
25 | }
26 |
27 | modelEvents() {
28 | return {
29 | 'change:sharedWith': 'render',
30 | };
31 | }
32 |
33 | collectionEvents() {
34 | return {
35 | 'add change update': 'render',
36 | };
37 | }
38 |
39 | /**
40 | * Trigger "share" method.
41 | *
42 | * @param {Object} e
43 | */
44 | share(e) {
45 | const username = this.$(e.currentTarget).attr('data-user');
46 | this.triggerMethod('share', {username});
47 | }
48 |
49 | serializeData() {
50 | return this.options;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/app/scripts/components/share/users/template.html:
--------------------------------------------------------------------------------
1 | <% if (!_.isEmpty(collection.models)) { %>
2 | {{_.i18n('Users you trust')}}:
3 |
4 | <% _.each(collection.getTrusted(), function(user) { %>
5 |
6 |
7 | <% if (model.isSharedWith(user.get('username'))) { %>
8 |
11 | <% } else { %>
12 |
15 | <% } %>
16 |
17 |
18 |
19 | {{user.get('username')}}
20 | <% if (user.get('pendingInvite')) { %>
21 | {{_.i18n('pending invite')}}
22 | <% } %>
23 |
24 |
{{_.i18n('Key fingerprint')}}: {{_.splitBy4(user.get('fingerprint'))}}
25 |
26 |
27 | <% }) %>
28 |
29 | <% } %>
30 |
31 |
32 | {{_.i18n('Please note that when you share a document with someone, you give them read and write access.')}}
33 |
34 |
--------------------------------------------------------------------------------
/app/scripts/constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Configs that don't change.
3 | *
4 | * @module constants
5 | * @license MPL-2.0
6 | */
7 | import _ from 'underscore';
8 |
9 | /**
10 | * @namespace constants
11 | * @prop {String} version - current version of the app
12 | * @prop {String} url - the URL where the app is accessed from
13 | * @prop {Array} defaultHosts - hosts that Dropbox auth server accepts
14 | * @prop {String} dropboxKey - dropbox API key
15 | * @prop {String} dropboxSecret - dropbox secret key (not necessary)
16 | * @prop {Boolean} dropboxKeyNeed - true if the default Dropbox API key will
17 | * not work and a user needs to provid their own
18 | */
19 | const constants = {
20 | version : '1.0.3-beta',
21 | url : location.origin + location.pathname.replace('index.html', ''),
22 | defaultHosts : [
23 | 'privatenote-online.com',
24 | 'laverna.github.io',
25 | 'localhost',
26 | 'localhost:9000',
27 | 'localhost:9100',
28 | ],
29 | dropboxKey : 'hlicys9cs8rj3ep',
30 | dropboxSecret : null,
31 | dropboxKeyNeed: false,
32 | };
33 |
34 | // The default Dropbox API key will not work
35 | if (!_.contains(constants.defaultHosts, location.host) && !window.electron) {
36 | constants.dropboxKeyNeed = true;
37 | }
38 |
39 | export default constants;
40 |
--------------------------------------------------------------------------------
/app/scripts/models/Config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module models/Config
3 | */
4 | import Model from './Model';
5 |
6 | /**
7 | * Config model.
8 | *
9 | * @class
10 | * @extends module:models/Model
11 | * @license MPL-2.0
12 | */
13 | export default class Config extends Model {
14 |
15 | /**
16 | * Use name as ID.
17 | *
18 | * @returns {String}
19 | */
20 | get idAttribute() {
21 | return 'name';
22 | }
23 |
24 | /**
25 | * Default values.
26 | *
27 | * @returns {Object}
28 | */
29 | get defaults() {
30 | return {
31 | name : '',
32 | value : '',
33 | };
34 | }
35 |
36 | /**
37 | * Store name.
38 | *
39 | * @returns {String}
40 | */
41 | get storeName() {
42 | return 'configs';
43 | }
44 |
45 | get validateAttributes() {
46 | return ['name'];
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/app/scripts/models/File.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module models/File
3 | */
4 | import Model from './Model';
5 |
6 | /**
7 | * File model.
8 | *
9 | * @class
10 | * @extends module:models/Model
11 | * @license MPL-2.0
12 | */
13 | export default class File extends Model {
14 |
15 | /**
16 | * Store name.
17 | *
18 | * @returns {String}
19 | */
20 | get storeName() {
21 | return 'files';
22 | }
23 |
24 | /**
25 | * Default values.
26 | *
27 | * @property {String} type - type of data stored in this model
28 | * @property {(String|Undefined)} id - undefined for default
29 | * @property {String} name - name of the stored file
30 | * @property {String} src - file data (blob?)
31 | * @property {String} fileType - (jpg|png|pdf|...)
32 | * @property {Number} trash - 1 if the model is in trash
33 | * @property {Date.now()} created - the date when the model was created
34 | * @property {Date.now()} update - the date when the model was updated
35 | * the last time
36 | */
37 | get defaults() {
38 | return {
39 | type : 'files',
40 | id : undefined,
41 | name : '',
42 | src : '',
43 | fileType : '',
44 | trash : 0,
45 | created : 0,
46 | updated : 0,
47 | };
48 | }
49 |
50 | get validateAttributes() {
51 | return ['src', 'fileType'];
52 | }
53 |
54 | get escapeAttributes() {
55 | return ['name'];
56 | }
57 |
58 | /**
59 | * @todo implement a proper check
60 | */
61 | isSharedWith() {
62 | return true;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/scripts/models/Profile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module models/Profile
3 | */
4 | import Model from './Model';
5 |
6 | /**
7 | * Profile model.
8 | *
9 | * @class
10 | * @extends module:models/Model
11 | * @license MPL-2.0
12 | */
13 | export default class Profile extends Model {
14 |
15 | get storeName() {
16 | return 'profiles';
17 | }
18 |
19 | /**
20 | * Use username as ID.
21 | *
22 | * @returns {String}
23 | */
24 | get idAttribute() {
25 | return 'username';
26 | }
27 |
28 | /**
29 | * Default values.
30 | *
31 | * @prop {String} username
32 | * @prop {String} privateKey - private key
33 | * @prop {String} publicKey - public key
34 | * @returns {Object}
35 | */
36 | get defaults() {
37 | return {
38 | username : '',
39 | privateKey : '',
40 | publicKey : '',
41 | };
42 | }
43 |
44 | get validateAttributes() {
45 | return ['username'];
46 | }
47 |
48 | get escapeAttributes() {
49 | return ['username'];
50 | }
51 |
52 | constructor(options) {
53 | super(options, {profileId: 'default'});
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/app/scripts/models/Tag.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module models/Tag
3 | */
4 | import Model from './Model';
5 |
6 | /**
7 | * Tag model.
8 | *
9 | * @class
10 | * @extends module:models/Model
11 | * @license MPL-2.0
12 | */
13 | export default class Tag extends Model {
14 |
15 | get storeName() {
16 | return 'tags';
17 | }
18 |
19 | /**
20 | * Default values.
21 | *
22 | * @property {String} type - type of data stored in this model
23 | * @property {(String|Undefined)} id - undefined for default
24 | * @property {String} encryptedData
25 | * @property {String} name - the name of a tag
26 | * @property {Number} count - the number of notes attached to a tag
27 | * @property {Number} trash
28 | * @property {Date.now()} created
29 | * @property {Date.now()} updated
30 | * @returns {Object}
31 | */
32 | get defaults() {
33 | return {
34 | type : 'tags',
35 | id : undefined,
36 | encryptedData : '',
37 | name : '',
38 | count : 0,
39 | trash : 0,
40 | created : 0,
41 | updated : 0,
42 | };
43 | }
44 |
45 | /**
46 | * Attributes that need to be encrypted.
47 | *
48 | * @returns {Array}
49 | */
50 | get encryptKeys() {
51 | return ['name'];
52 | }
53 |
54 | get validateAttributes() {
55 | return ['name'];
56 | }
57 |
58 | get escapeAttributes() {
59 | return ['name'];
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/app/scripts/models/User.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module models/User
3 | */
4 | import Model from './Model';
5 |
6 | /**
7 | * User model.
8 | * Stores information about users with whom you want to connect.
9 | *
10 | * @class
11 | * @extends module:models/Model
12 | * @license MPL-2.0
13 | */
14 | export default class User extends Model {
15 |
16 | get storeName() {
17 | return 'users';
18 | }
19 |
20 | /**
21 | * Use username as ID attribute.
22 | *
23 | * @prop {String}
24 | */
25 | get idAttribute() {
26 | return 'username';
27 | }
28 |
29 | /**
30 | * Default values.
31 | *
32 | * @prop {String} type - equal to users
33 | * @prop {String} username
34 | * @prop {String} fingerprint
35 | * @prop {String} publicKey
36 | * @prop {Boolean} pendingInvite - true if the user was invited by you
37 | * and you're waiting for their answer
38 | * @prop {Boolean} pendingAccept - true if you have been invited by this
39 | * user and they're waiting for your answer
40 | * @returns {Object}
41 | */
42 | get defaults() {
43 | return {
44 | type : 'users',
45 | username : '',
46 | fingerprint : '',
47 | publicKey : '',
48 | pendingAccept : false,
49 | pendingInvite : false,
50 | };
51 | }
52 |
53 | get validateAttributes() {
54 | return ['username', 'publicKey', 'fingerprint'];
55 | }
56 |
57 | get escapeAttributes() {
58 | return ['username', 'publicKey', 'fingerprint'];
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/scripts/models/diffsync/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module models/diffsync/main
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 | import Signal from '../Signal';
7 | import Peer from '../Peer';
8 | import Core from './Core';
9 |
10 | export default function initializer() {
11 | // Instantiate the class that connects a user to the signaling server
12 | const signal = new Signal();
13 |
14 | function start() {
15 | const sync = Radio.request('collections/Configs', 'findConfig', {
16 | name: 'cloudStorage',
17 | });
18 |
19 | // intentionally breaking signal server for now. no p2p until it works.
20 | sync == 'none';
21 | if (sync === 'p2p') {
22 | signal.changeServer();
23 | new Peer().init();
24 | new Core().init();
25 | }
26 | else {
27 | signal.destroy();
28 | }
29 | }
30 |
31 | // Initialize peer class and differential synchronization core
32 | Radio.once('App', 'start', start);
33 | return start;
34 | }
35 |
36 | // Add a new initializer
37 | Radio.once('App', 'init', () => {
38 | Radio.request('utils/Initializer', 'add', {
39 | name : 'App:utils',
40 | callback: initializer,
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/app/scripts/modules/fs/module.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2015 Laverna project Authors.
3 | *
4 | * This Source Code Form is subject to the terms of the Mozilla Public
5 | * License, v. 2.0. If a copy of the MPL was not distributed with this
6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 | */
8 | /* global define */
9 | define([
10 | 'underscore',
11 | 'backbone.radio',
12 | 'marionette',
13 | 'modules',
14 | 'modules/fs/classes/sync',
15 | ], (_, Radio, Marionette, Modules, Sync) => {
16 | 'use strict';
17 |
18 | /**
19 | * Module which synchronizes all models to a file system.
20 | * (Works only on Electron app)
21 | */
22 | const FS = Modules.module('FS', {});
23 |
24 | /**
25 | * Initializers & finalizers of the module
26 | */
27 | FS.on('start', () => {
28 | console.info('FS started');
29 | new Sync();
30 | });
31 |
32 | FS.on('stop', () => {
33 | });
34 |
35 | // Add a global module initializer
36 | Radio.request('init', 'add', 'module', () => {
37 | FS.start();
38 | });
39 |
40 | return FS;
41 | });
42 |
--------------------------------------------------------------------------------
/app/scripts/modules/fs/templates/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/scripts/modules/fs/views/settings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2015 Laverna project Authors.
3 | *
4 | * This Source Code Form is subject to the terms of the Mozilla Public
5 | * License, v. 2.0. If a copy of the MPL was not distributed with this
6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 | */
8 | /* global define, requireNode */
9 | define([
10 | 'underscore',
11 | 'marionette',
12 | 'backbone.radio',
13 | 'text!modules/fs/templates/settings.html',
14 | ], (_, Marionette, Radio, Tmpl) => {
15 | 'use strict';
16 |
17 | /**
18 | * Shows FS module's configs.
19 | */
20 | const dialog = requireNode('electron').remote.dialog;
21 |
22 | const View = Marionette.ItemView.extend({
23 | template: _.template(Tmpl),
24 |
25 | ui: {
26 | input: '.input--fs',
27 | },
28 |
29 | events : {
30 | 'click .btn--fs': 'showFolderDialog',
31 | },
32 |
33 | initialize() {
34 | },
35 |
36 | serializeData() {
37 | return {
38 | models: this.collection.getConfigs(),
39 | };
40 | },
41 |
42 | showFolderDialog(e) {
43 | e.preventDefault();
44 |
45 | const folder = dialog.showOpenDialog({properties: ['openDirectory']});
46 |
47 | if (!folder) {
48 | return;
49 | }
50 |
51 | this.ui.input.val(folder[0]);
52 |
53 | this.collection.trigger('new:value', {
54 | name : 'module:fs:folder',
55 | value : folder[0],
56 | });
57 | },
58 |
59 | });
60 |
61 | return View;
62 | });
63 |
--------------------------------------------------------------------------------
/app/scripts/modules/modules.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id" : "mathjax",
4 | "name" : "MathJax",
5 | "description" : "MathJax",
6 | "minVersion" : "",
7 | "maxVersion" : "",
8 | "platforms" : [
9 | "electron",
10 | "browser",
11 | "mobile"
12 | ]
13 | },
14 | {
15 | "id" : "fuzzySearch",
16 | "name" : "fuzzySearch",
17 | "description" : "fuzzySearch module",
18 | "minVersion" : "",
19 | "maxVersion" : "",
20 | "platforms" : [
21 | "electron",
22 | "browser",
23 | "mobile"
24 | ]
25 | },
26 | {
27 | "id" : "fs",
28 | "name" : "FS",
29 | "description" : "Synchronizes everything to the file system",
30 | "hasSettings" : true,
31 | "minVersion" : "",
32 | "maxVersion" : "",
33 | "platforms" : [
34 | "electron"
35 | ]
36 | },
37 | {
38 | "id" : "electronSearch",
39 | "name" : "Electron Search",
40 | "description" : "",
41 | "hasSettings" : false,
42 | "minVersion" : "",
43 | "maxVersion" : "",
44 | "platforms" : [
45 | "electron"
46 | ]
47 | }
48 | ]
49 |
--------------------------------------------------------------------------------
/app/scripts/modules/remotestorage/classes/rs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2015 Laverna project Authors.
3 | *
4 | * This Source Code Form is subject to the terms of the Mozilla Public
5 | * License, v. 2.0. If a copy of the MPL was not distributed with this
6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 | */
8 | /* global define, RemoteStorage */
9 | define([
10 | 'tv4',
11 | 'backbone.radio',
12 | 'remotestorage',
13 | ], (tv4, Radio) => {
14 | 'use strict';
15 |
16 | // Make TV4 globally available because RemoteStorage needs it.
17 | window.tv4 = tv4;
18 | const RS = new RemoteStorage({
19 | logging : false,
20 | cordovaRedirectUri : 'https://laverna.cc',
21 | changeEvents : {
22 | local : false,
23 | window : false,
24 | remote : true,
25 | conflict : true,
26 | },
27 | });
28 |
29 |
30 | /**
31 | * Sometimes hash is not saved automatically after starting Backbone router.
32 | */
33 | let md = Radio.request('global', 'hash:original');
34 | md = md.match(/access_token=([^&]+)/);
35 | if (md && !RS.remote.token) {
36 | RS.remote.configure({
37 | token: md[1],
38 | });
39 | }
40 |
41 | // Make remoteStorage globally available
42 | window.remoteStorage = RS;
43 | return window.remoteStorage;
44 | });
45 |
--------------------------------------------------------------------------------
/app/scripts/modules/remotestorage/module.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2015 Laverna project Authors.
3 | *
4 | * This Source Code Form is subject to the terms of the Mozilla Public
5 | * License, v. 2.0. If a copy of the MPL was not distributed with this
6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 | */
8 | /* global define */
9 | define([
10 | 'underscore',
11 | 'backbone.radio',
12 | 'marionette',
13 | 'modules',
14 | 'modules/remotestorage/classes/sync',
15 | ], (_, Radio, Marionette, Modules, Sync) => {
16 | 'use strict';
17 |
18 | const RemoteStorage = Modules.module('RemoteStorage', {});
19 |
20 | /**
21 | * Initializers & finalizers of the module
22 | */
23 | RemoteStorage.on('start', () => {
24 | console.info('RemoteStorage started');
25 | new Sync();
26 | });
27 |
28 | RemoteStorage.on('stop', () => {
29 | });
30 |
31 | // Add a global module initializer
32 | Radio.request('init', 'add', 'module', () => {
33 | RemoteStorage.start();
34 | });
35 |
36 | return RemoteStorage;
37 | });
38 |
--------------------------------------------------------------------------------
/app/scripts/templates/layout.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/scripts/templates/loader.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/scripts/utils/electronListener.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module utils/electronListener
3 | * @license MPL-2.0
4 | */
5 | import Radio from 'backbone.radio';
6 |
7 | /**
8 | * Listen to events sent by the main Electron process.
9 | */
10 | function electronListener() {
11 | // Do nothing if it isn't Electron environment
12 | if (!window.electron) {
13 | return false;
14 | }
15 |
16 | const urlChannel = Radio.channel('utils/Url');
17 | const {ipcRenderer} = window.electron;
18 |
19 | ipcRenderer.on('lav:settings', () => {
20 | urlChannel.request('navigate', {url: '/settings'});
21 | });
22 |
23 | ipcRenderer.on('lav:newNote', () => {
24 | urlChannel.request('navigate', {url: '/notes/add'});
25 | });
26 |
27 | ipcRenderer.on('lav:about', () => {
28 | Radio.request('components/help', 'showAbout');
29 | });
30 |
31 | ipcRenderer.on('lav:import:evernote', (e, data) => {
32 | Radio.request('components/importExport', 'importEvernote', data);
33 | });
34 |
35 | ipcRenderer.on('lav:backup:key', () => {
36 | Radio.request('components/importExport', 'export', {exportKey: true});
37 | });
38 |
39 | ipcRenderer.on('lav:backup:data', () => {
40 | Radio.request('components/importExport', 'export');
41 | });
42 |
43 | return true;
44 | }
45 |
46 | Radio.once('App', 'init', () => {
47 | Radio.request('utils/Initializer', 'add', {
48 | name : 'App:utils',
49 | callback: electronListener,
50 | });
51 | });
52 |
53 | export default electronListener;
54 |
--------------------------------------------------------------------------------
/app/scripts/utils/fileSaver.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module utils/fileSaver
3 | * @license MPL-2.0
4 | */
5 | import {saveAs} from 'file-saver';
6 |
7 | /**
8 | * Save a file in Cordova environment.
9 | *
10 | * @param {String} content
11 | * @param {String} fileName
12 | * @param {Function} resolve
13 | */
14 | function cordovaSave(content, fileName, resolve) {
15 | const externalDir = window.cordova.file.externalDataDirectory;
16 |
17 | window.resolveLocalFileSystemURL(externalDir, dir => {
18 | dir.getFile(fileName, {create: true}, file => {
19 | file.createWriter(writer => {
20 | writer.write(content);
21 | resolve();
22 | });
23 | });
24 | });
25 | }
26 |
27 | /**
28 | * Save a file.
29 | *
30 | * @param {String} content
31 | * @param {String} fileName
32 | * @returns {Promise}
33 | */
34 | function fileSaver(content, fileName) {
35 | if (window.cordova) {
36 | return new Promise(resolve => cordovaSave(content, fileName, resolve));
37 | }
38 |
39 | // Use HTML5 saveAs
40 | return Promise.resolve(saveAs(content, fileName));
41 | }
42 |
43 | export {fileSaver as default, cordovaSave};
44 |
--------------------------------------------------------------------------------
/app/scripts/utils/theme.js:
--------------------------------------------------------------------------------
1 | import Radio from 'backbone.radio';
2 |
3 | const theme = {
4 | /**
5 | * Apply a theme.
6 | *
7 | * @param {Object} data
8 | * @param {String} data.name - theme name
9 | */
10 | applyTheme(data = {}) {
11 | const theme = data.name || Radio.request('collections/Configs', 'findConfig', {
12 | name: 'theme',
13 | });
14 | $('#lav--theme').attr('href', `styles/theme-${theme || 'default'}.css`);
15 | },
16 |
17 | /**
18 | * Initializer.
19 | */
20 | initializer() {
21 | Radio.on('components/settings', 'changeTheme', theme.applyTheme);
22 | theme.applyTheme();
23 | },
24 | };
25 |
26 | Radio.once('App', 'init', () => {
27 | Radio.request('utils/Initializer', 'add', {
28 | name : 'App:utils',
29 | callback: theme.initializer,
30 | });
31 | });
32 |
33 | export default theme;
34 |
--------------------------------------------------------------------------------
/app/scripts/views/Brand.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module views/Brand
3 | */
4 | import Mn from 'backbone.marionette';
5 |
6 | /**
7 | * The region that shows its content on fullscreen and on green background.
8 | *
9 | * @class
10 | * @extends Marionette.Region
11 | * @license MPL-2.0
12 | */
13 | export default class Brand extends Mn.Region {
14 |
15 | /**
16 | * Show the region with animation.
17 | */
18 | onShow() {
19 | this.$el.slideDown('fast');
20 | }
21 |
22 | /**
23 | * Hide the region with animation.
24 | */
25 | onEmpty() {
26 | this.$el.slideUp('fast');
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/app/scripts/views/Loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module views/Loader
3 | */
4 | import Mn from 'backbone.marionette';
5 | import _ from 'underscore';
6 |
7 | /**
8 | * A view that shows a spining icon.
9 | *
10 | * @class
11 | * @extends Marionette.View
12 | * @license MPL-2.0
13 | */
14 | export default class Loader extends Mn.View {
15 |
16 | get template() {
17 | const tmpl = require('../templates/loader.html');
18 | return _.template(tmpl);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/styles/core/codemirror.less:
--------------------------------------------------------------------------------
1 | @import 'codemirror/core.less';
2 | @import 'codemirror/theme.less';
3 |
--------------------------------------------------------------------------------
/app/styles/core/codemirror/theme.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Default theme for Codemirror.
3 | */
4 |
5 | /**
6 | * Headers
7 | */
8 | .cm-s-default .cm-header {
9 | color: #000;
10 | }
11 | .cm-s-default .cm-header-1 {
12 | &:extend(h1);
13 | line-height: floor((@font-size-h1 * @line-height-base));
14 | }
15 | .cm-s-default .cm-header-2 {
16 | &:extend(h2);
17 | line-height: floor((@font-size-h2 * @line-height-base));
18 | }
19 | .cm-s-default .cm-header-3 {
20 | &:extend(h3);
21 | line-height: floor((@font-size-h3 * @line-height-base));
22 | }
23 | .cm-s-default .cm-header-4 {
24 | &:extend(h4);
25 | line-height: floor((@font-size-h4 * @line-height-base));
26 | }
27 | .cm-s-default .cm-header-5 {
28 | &:extend(h5);
29 | line-height: floor((@font-size-h5 * @line-height-base));
30 | }
31 | .cm-s-default .cm-header-6 {
32 | &:extend(h6);
33 | line-height: floor((@font-size-h6 * @line-height-base));
34 | }
35 |
36 | /**
37 | * Other styles
38 | */
39 | .CodeMirror-composing {
40 | border-bottom: 2px solid;
41 | }
42 |
43 | .cm-header,
44 | .cm-strong:extend(strong) {}
45 |
46 | .cm-em {
47 | font-style: italic;
48 | }
49 |
50 | .cm-strikethrough {
51 | text-decoration: line-through;
52 | }
53 |
54 | .cm-link {
55 | text-decoration: underline;
56 | }
57 |
58 | /**
59 | * Default styles for common addons
60 | */
61 | div.CodeMirror span.CodeMirror-matchingbracket {
62 | color: #0f0;
63 | }
64 | div.CodeMirror span.CodeMirror-nonmatchingbracket {
65 | color: #f22;
66 | }
67 | .CodeMirror-matchingtag {
68 | background: rgba(255, 150, 0, .3);
69 | }
70 | .CodeMirror-activeline-background {
71 | background: #e8f2ff;
72 | }
73 |
--------------------------------------------------------------------------------
/app/styles/core/fontello.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Fontello
3 | */
4 | @import 'fontello/css/fontello.less';
5 | @import 'fontello/css/fontello-codes.less';
6 | // @import 'fontello/css/fontello-ie7-codes.less';
7 | @import 'fontello/css/fontello-embedded.less';
8 | // @import 'fontello/css/fontello-ie7.less';
9 | @import 'fontello/css/animation.less';
10 |
11 | .icons {
12 | font-family: "fontello";
13 | }
14 | .icon--block {
15 | display: block;
16 | }
17 |
--------------------------------------------------------------------------------
/app/styles/core/fontello/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Font license info
2 |
3 |
4 | ## Font Awesome
5 |
6 | Copyright (C) 2012 by Dave Gandy
7 |
8 | Author: Dave Gandy
9 | License: SIL ()
10 | Homepage: http://fortawesome.github.com/Font-Awesome/
11 |
12 |
13 | ## Entypo
14 |
15 | Copyright (C) 2012 by Daniel Bruce
16 |
17 | Author: Daniel Bruce
18 | License: SIL (http://scripts.sil.org/OFL)
19 | Homepage: http://www.entypo.com
20 |
21 |
22 | ## Elusive
23 |
24 | Copyright (C) 2013 by Aristeides Stathopoulos
25 |
26 | Author: Aristeides Stathopoulos
27 | License: SIL (http://scripts.sil.org/OFL)
28 | Homepage: http://aristeides.com/
29 |
30 |
31 | ## Modern Pictograms
32 |
33 | Copyright (c) 2012 by John Caserta. All rights reserved.
34 |
35 | Author: John Caserta
36 | License: SIL (http://scripts.sil.org/OFL)
37 | Homepage: http://thedesignoffice.org/project/modern-pictograms/
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/styles/core/fontello/font/fontello.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/styles/core/fontello/font/fontello.eot
--------------------------------------------------------------------------------
/app/styles/core/fontello/font/fontello.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/styles/core/fontello/font/fontello.ttf
--------------------------------------------------------------------------------
/app/styles/core/fontello/font/fontello.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daed/laverna/592e89d32120200e33cde5bc04a9a7fcff400001/app/styles/core/fontello/font/fontello.woff
--------------------------------------------------------------------------------
/app/styles/core/fuzzy.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Fuzzy search
3 | */
4 | // Hide other content in sidebar while Fuzzy search is active
5 | .-fuzzy #sidebar--content {
6 | display: none;
7 | }
8 |
--------------------------------------------------------------------------------
/app/styles/core/list.less:
--------------------------------------------------------------------------------
1 | /**
2 | * List module
3 | */
4 | .list {
5 | }
6 |
7 | .list--group {
8 | margin-bottom: 0;
9 | position: relative;
10 |
11 | &:last-child .list--item {
12 | border-bottom-width: 1px;
13 | }
14 | }
15 |
16 | .list--item {
17 | border-right-width : 0;
18 | border-bottom-width : 0;
19 | }
20 |
21 | .list--item.-note {
22 | min-height: 60px;
23 | max-height: 60px;
24 | }
25 |
26 | .list--item--title.-note {
27 | overflow : hidden;
28 | white-space : nowrap;
29 | width : 90%;
30 | &:extend(.pull-left);
31 | }
32 |
33 | .list--item--favorite {
34 | width: 7%;
35 | &:extend(.pull-right);
36 | }
37 |
38 | .list--item--text {
39 | max-width: 80%;
40 | &:extend(.-text-elipsis);
41 | }
42 |
43 | // Button groups
44 | .list--buttons {
45 | position : absolute;
46 | top : 0;
47 | right : 4px;
48 | padding : 8px 4px 8px 0;
49 | }
50 |
51 | // Navbars
52 | .list--navbar {
53 | z-index : inherit;
54 | margin-bottom : 0;
55 | padding-top : 20px;
56 | }
57 | .list--nav {
58 | margin-right: 2px;
59 | }
60 |
61 | // Pagination bar
62 | .list--pager {
63 | margin-top : 15px;
64 | margin-left : 10px;
65 | margin-right : 10px;
66 | }
67 |
--------------------------------------------------------------------------------
/app/styles/core/main.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Core styles.
3 | */
4 |
5 | // Fontello
6 | @import 'fontello.less';
7 |
8 | // Import Bootstrap
9 | @import 'bootstrap.less';
10 | @import 'variables.less';
11 |
12 | // Navbars
13 | @import 'header.less';
14 | @import 'sidemenu.less';
15 |
16 | // Sidebar
17 | @import 'list.less';
18 | @import 'fuzzy.less';
19 |
20 | // Editor
21 | @import 'codemirror.less';
22 | @import 'editor.less';
23 |
24 | // Core
25 | @import 'layout.less';
26 | @import 'utils.less';
27 | @import 'responsive.less';
28 |
29 | // RemoteStorage styles
30 | #remotestorage-widget {
31 | top : auto !important;
32 | right : 15px !important;
33 | bottom : 80px !important;
34 |
35 | &.remotestorage-state-connected {
36 | bottom: 40px !important;
37 | }
38 | }
39 |
40 | .container--white {
41 | background-color: #FFF;
42 | border-radius: 4px;
43 | color: darken(#FFF, 70%);
44 | > .row {
45 | padding: 30px 10px;
46 | }
47 | .input--brand {
48 | color: darken(#FFF, 60%);
49 | &::placeholder {
50 | color: darken(#FFF, 40%);
51 | }
52 | &:hover {
53 | border-color: darken(#FFF, 10%);
54 | }
55 | &:focus,
56 | &:active {
57 | border-color: darken(#FFF, 50%);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/styles/core/responsive.less:
--------------------------------------------------------------------------------
1 | .make-visible(@resolution) {
2 | @media (max-width: @resolution) {
3 | .responsive-visibility();
4 | }
5 | }
6 |
7 | .visible-500,
8 | .visible-400 {
9 | .responsive-invisibility();
10 | }
11 |
12 | .visible-500 {
13 | .make-visible(500px);
14 | }
15 | .visible-400 {
16 | .make-visible(400px);
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/app/styles/core/sidemenu.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Side menu
3 | */
4 | .sidemenu {
5 | .make-xs-column(8);
6 | .make-md-column(3);
7 | .make-sm-column(3);
8 | .make-lg-column(3);
9 | }
10 | .sidemenu {
11 | background-color: #fff;
12 |
13 | // Position and z-index
14 | position : fixed;
15 | top : 0;
16 | bottom : 0;
17 | z-index : @zindex--sidemenu;
18 |
19 | // Size
20 | height : 100%;
21 | padding : 0;
22 |
23 | // Show scrollbars
24 | overflow-y : auto;
25 | overflow-x : hidden;
26 |
27 | // Slide open from left
28 | visibility : hidden;
29 | transition : all 0.3s ease;
30 | .make-xs-column-push(-8);
31 | .make-md-column-push(-3);
32 | .make-sm-column-push(-3);
33 | .make-lg-column-push(-3);
34 |
35 | &.-show {
36 | visibility: visible;
37 | left: 0;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/styles/core/utils.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Utility classes
3 | */
4 | // Show scrollbars
5 | .-noscroll {
6 | overflow: hidden;
7 | }
8 | .-scroll {
9 | overflow: hidden;
10 | overflow-y: scroll;
11 | }
12 | .-scroll-x {
13 | overflow-x: scroll;
14 | }
15 | .-relative {
16 | // position: relative;
17 | }
18 |
19 | // Hide text that doesn't fit
20 | .-text-elipsis {
21 | overflow: hidden;
22 | white-space: nowrap;
23 | text-overflow: ellipsis;
24 | }
25 |
26 | // Default view for all tables inside of notes
27 | .layout--body.-note,
28 | .layout--body.-form {
29 | table {
30 | &:extend(.table all);
31 | &:extend(.table-bordered all);
32 | &:extend(.table-striped all);
33 | &:extend(.table-hover all);
34 | width: auto;
35 | }
36 | img {
37 | &:extend(.img-responsive);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/styles/core/variables.less:
--------------------------------------------------------------------------------
1 | /**
2 | */
3 | // Basic variables
4 | @grid-float-breakpoint: 0;
5 | @border-radius-base: 3px;
6 |
7 | // List group
8 | @list-group-border-radius: 0;
9 |
10 | // Z-indexes
11 | @zindex--backdrop: @zindex-navbar-fixed + 1;
12 | @zindex--sidemenu: @zindex-navbar-fixed + 2;
13 |
--------------------------------------------------------------------------------
/app/styles/theme-dark/main.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Default theme
3 | */
4 | @import "bootstrap/less/variables.less";
5 | @import "bootstrap/less/mixins.less";
6 |
7 | // Variables
8 | @import '../theme-default/variables.less';
9 |
10 | // Core
11 | @import '../theme-default/layout.less';
12 | @import '../theme-default/utils.less';
13 | @import '../theme-default/forms.less';
14 | @import '../theme-default/modal.less';
15 | @import '../theme-default/buttons.less';
16 | @import '../theme-default/checkbox.less';
17 |
18 | // Navbars
19 | @import '../theme-default/header.less';
20 | @import '../theme-default/sidemenu.less';
21 |
22 | // List of notes, notebooks, etc.
23 | @import '../theme-default/list.less';
24 |
25 | // Settings
26 | @import '../theme-default/settings.less';
27 |
28 | // Codemirror editor
29 | @import '../theme-default/editor.less';
30 | @import '../theme-default/codemirror.less';
31 | @import '../theme-default/dropzone.less';
32 |
33 | // Loading animation
34 | @import '../theme-default/loading-animation.less';
35 |
36 | @import 'prism.less';
37 | @import 'theme.less';
38 |
--------------------------------------------------------------------------------
/app/styles/theme-default/buttons.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Buttons
3 | */
4 | .btn-default {
5 | border-color: @btn--border--color;
6 | }
7 |
8 | // Success button
9 | .btn.btn-success {
10 | background-color : @btn--success;
11 | border-color : @btn--success--border;
12 | &:hover,
13 | &:focus {
14 | background-color: @btn--success--hover;
15 | }
16 | }
17 |
18 | /**
19 | * Change button styles in note view
20 | */
21 | .btn.-note {
22 | background : @btn--note--bg;
23 | border : none;
24 | box-shadow : none;
25 | padding : 7px 15px;
26 | margin-right : 5px;
27 | &:hover {
28 | background: @btn--note--hover;
29 | }
30 | }
31 |
32 | /**
33 | * Favorite button
34 | */
35 | .btn--favorite {
36 | color : @btn--favorite;
37 | font-size : 1em;
38 | padding : 0;
39 | &:hover,
40 | &:focus {
41 | outline : none;
42 | color : @btn--favorite--hover;
43 | }
44 | }
45 |
46 | /**
47 | * WYSIWYG buttons
48 | */
49 | .btn--wysiwyg {
50 | border-radius : 0;
51 | transition : background .3s ease-in-out;
52 |
53 | &:hover {
54 | background : @pagedown--btn--hover--bg;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/styles/theme-default/checkbox.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Checkbox module
3 | */
4 | .task--checkbox {
5 | position : relative;
6 | cursor : pointer;
7 | &:hover {
8 | color: lighten(@text-color, 15%);
9 | }
10 | }
11 |
12 | // Text
13 | .checkbox--text {
14 | margin-left: 10px;
15 | font-weight: 300;
16 |
17 | // Create checkbox border block
18 | &::before {
19 | content : ' ';
20 | width : @check--size;
21 | height : @check--size;
22 |
23 | // Borders
24 | border : @check--border;
25 | border-radius : @check--border-radius;
26 |
27 | // Position
28 | position : absolute;
29 | left : 0;
30 | top : 3px;
31 | }
32 | }
33 |
34 | .checkbox--svg {
35 | width : @check--icon--size;
36 | height : @check--icon--size;
37 | position : absolute;
38 | top : 6px;
39 | left : 3px;
40 | }
41 |
42 | .checkbox--path {
43 | fill : none;
44 | stroke : @check--color;
45 | stroke-width : 20px;
46 | stroke-linecap : round;
47 |
48 | stroke-dasharray : 126.366, 126.366;
49 | stroke-dashoffset : 126.6;
50 | transition : stroke-dashoffset 0.2s ease-in-out 0s;
51 | stroke-linejoin : round;
52 | }
53 |
54 | .checkbox--input {
55 | opacity: 0;
56 |
57 | &:checked {
58 | & ~ .checkbox--text {
59 | text-decoration: line-through;
60 | }
61 | & + svg path {
62 | stroke-dashoffset: 0;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/styles/theme-default/codemirror.less:
--------------------------------------------------------------------------------
1 | .CodeMirror {
2 | background : white;
3 | }
4 |
5 | .cm-link {
6 | color: @gray;
7 | }
8 | .cm-url {
9 | color: @gray-light;
10 | }
11 |
12 | .cm-comment {
13 | background-color: lighten(@gray-lighter, 2);
14 | }
15 |
16 | .cm-quote {
17 | color: @gray-light;
18 | }
19 |
--------------------------------------------------------------------------------
/app/styles/theme-default/dropzone.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Dropzone styles.
3 | */
4 | .dropzone--container {
5 | margin: 20px auto;
6 | border: 10px dashed lighten(#ccc, 8%);
7 | }
8 |
9 | .dropzone {
10 | min-height : 100px;
11 | cursor : pointer;
12 | padding : 20px;
13 |
14 | .dz-default.dz-message {
15 | margin-bottom : 16px;
16 | text-align : center;
17 | }
18 |
19 | .dz-details {
20 | text-align: center;
21 | }
22 |
23 | .dz-preview {
24 | margin-right : 5px;
25 | margin-bottom : 5px;
26 | padding : 4px;
27 | }
28 |
29 | .dz-image img {
30 | border-radius : 5px;
31 | cursor : auto;
32 | }
33 |
34 | .dz-filename {
35 | word-break: break-all;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/styles/theme-default/header.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Header module
3 | */
4 | .header--bg(@bg, @shadow, @border, @color) {
5 | background : @bg;
6 | box-shadow : @shadow;
7 | border : @border;
8 | color : @color;
9 |
10 | .header--title {
11 | &:hover,
12 | &:focus {
13 | background-color: darken(@bg, 5%);
14 | }
15 | }
16 |
17 | .navbar-nav > li > a,
18 | .navbar-btn,
19 | .navbar-link,
20 | .navbar-text,
21 | .btn-link {
22 | color : @color;
23 | &:hover,
24 | &:focus {
25 | color : @color;
26 | }
27 | }
28 | }
29 |
30 | /**
31 | * Left Navbar
32 | */
33 | .header.-left {
34 | .header--bg(@header--left--bg, @header--left--shadow, @header--left--border, @header--left--color);
35 | }
36 | .header--container.-left {
37 | padding-left: 0;
38 | }
39 | .header--search {
40 | padding-right: 0;
41 | }
42 |
43 | /**
44 | * Right Navbar
45 | */
46 | .header.-right,
47 | .header.-note {
48 | .header--bg(@header--right--bg, @header--right--shadow, @header--right--border, @header--right--color);
49 | }
50 |
51 | /**
52 | * The main navbar in note form, pagedown
53 | */
54 | .header.-form {
55 | .header--bg(@header--right--bg, @header--right--shadow, @header--right--border, @header--right--color);
56 | }
57 |
--------------------------------------------------------------------------------
/app/styles/theme-default/list.less:
--------------------------------------------------------------------------------
1 | /**
2 | * List module
3 | */
4 |
5 | .list--item {
6 | border-color: @list--border--color;
7 |
8 | &.active,
9 | &.active:hover,
10 | &.active:focus {
11 | background-color : @list--active--bg;
12 | border-color : @list--active--border;
13 | box-shadow : none;
14 | }
15 | }
16 |
17 | // List of notes
18 | .list--item.-note {
19 | padding-top : 25px;
20 | padding-bottom : 15px;
21 | max-height : 80px;
22 | min-height : 80px;
23 | }
24 |
25 | // Pagination buttons
26 | .list--pager--btn {
27 | padding : 15px;
28 | background : transparent;
29 | }
30 |
31 | // List of notebooks
32 | .list--nested {
33 | padding-left: 30px;
34 |
35 | .list--item {
36 | border-left-color: transparent;
37 | margin-left: -30px * 4;
38 | padding-left: 30px * 4;
39 | &:last-child {
40 | border-bottom-color: transparent;
41 | }
42 | }
43 | }
44 |
45 | // Navbars
46 | .list--navbar {
47 | color : @list--navbar--color;
48 | background : @list--navbar--bg;
49 | }
50 | .list--brand {
51 | font-variant : small-caps;
52 | color : @list--navbar--brand;
53 | }
54 |
--------------------------------------------------------------------------------
/app/styles/theme-default/main.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Default theme
3 | */
4 | @import "bootstrap/less/variables.less";
5 | @import "bootstrap/less/mixins.less";
6 |
7 | // Variables
8 | @import 'variables.less';
9 |
10 | // Core
11 | @import 'layout.less';
12 | @import 'utils.less';
13 | @import 'forms.less';
14 | @import 'modal.less';
15 | @import 'buttons.less';
16 | @import 'checkbox.less';
17 |
18 | // Navbars
19 | @import 'header.less';
20 | @import 'sidemenu.less';
21 |
22 | // List of notes, notebooks, etc.
23 | @import 'list.less';
24 |
25 | // Settings
26 | @import 'settings.less';
27 |
28 | // Codemirror editor
29 | @import 'editor.less';
30 | @import 'codemirror.less';
31 | @import 'dropzone.less';
32 |
33 | @import 'prism.less';
34 |
35 | // Loading animation
36 | @import 'loading-animation.less';
37 |
--------------------------------------------------------------------------------
/app/styles/theme-default/modal.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Modal windows
3 | */
4 | .modal--body.modal-body {
5 | padding: 0 15px;
6 | }
7 |
8 | .modal--group.form-group {
9 | border-bottom: 1px solid #e5e5e5;
10 | overflow: hidden;
11 | margin-left : -30px;
12 | margin-right : -30px;
13 | margin-bottom : 0;
14 | padding-top : 5px;
15 | padding-bottom : 5px;
16 | &:last-child {
17 | border-bottom-width: 0;
18 | }
19 | }
20 |
21 | .modal--input {
22 | border-width: 0;
23 |
24 | &, &:hover, &:focus {
25 | box-shadow: none;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/styles/theme-default/settings.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Settings
3 | */
4 | // List of settings
5 | .list--settings {
6 | &:extend(.sidemenu--item all);
7 |
8 | &.list-group-item.active {
9 | background : lighten(@sidemenu--item--hover, 2%);
10 | }
11 | }
12 |
13 | // Back button
14 | .header--settings--back:hover {
15 | background: #f8f8f8;
16 | }
17 |
18 | // Content
19 | .container-fluid.-settings {
20 | padding: 40px 30px;
21 | }
22 |
--------------------------------------------------------------------------------
/app/styles/theme-default/sidemenu.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Sidemenu
3 | */
4 | .sidemenu {
5 | background: @sidemenu--bg;
6 | }
7 |
8 | .sidemenu--item.list-group-item {
9 | // Sizing
10 | min-height : 50px;
11 | max-height : 50px;
12 | padding-left : 20px;
13 | padding-top : 15px;
14 |
15 | // Colors
16 | background : @sidemenu--item;
17 | color : @sidemenu--item--color;
18 | border : @sidemenu--item--border;
19 |
20 | &.active,
21 | &:hover {
22 | background : @sidemenu--item--hover;
23 | color : @sidemenu--item--hover--color;
24 | }
25 | }
26 |
27 | .sidemenu--item.-disabled,
28 | .sidemenu--item.-disabled:hover {
29 | background : @sidemenu--disabled;
30 | color : @sidemenu--disabled--color;
31 | font-size : 1.3em;
32 | }
33 |
34 | .sidemenu--item.list-child-item {
35 | padding-top : 0px;
36 | padding-bottom : 0px;
37 | min-height : 0px;
38 | }
39 |
40 | .label-branch {
41 | letter-spacing: -0.11em;
42 | }
43 | .label-branch-last {
44 | .label-branch;
45 | }
46 |
47 | /* Center right pipe symbol (verticle box middle right) */
48 | .label-branch:before {
49 | content: "\251C";
50 | /* line up with dashes */
51 | font-size: 10pt;
52 | }
53 |
54 | /* Bottom right pipe symbol (verticle box right) */
55 | .label-branch-last:before {
56 | content: "\2514";
57 | /* line up with dashes */
58 | font-size: 10pt;
59 | position: relative;
60 | top: 0.04em;
61 | }
62 |
63 | .label-branch-indent {
64 | display: inline-block;
65 | }
66 |
67 |
68 | .sidemenu--close {
69 | cursor: pointer;
70 | }
71 |
--------------------------------------------------------------------------------
/app/styles/theme-default/utils.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Utils
3 | */
4 | a,
5 | .btn-link {
6 | color: darken(@color--brand, 5%);
7 | &:hover {
8 | color: lighten(@color--brand, 5%);
9 | }
10 | }
11 |
12 | .-text-big {
13 | font-size : 1.3em;
14 | line-height : 2em;
15 | }
16 |
17 | // Progress bars
18 | .progress-bar-success {
19 | background-color: @color--brand;
20 | }
21 | .progress-bar-info {
22 | background-color: @color--info;
23 | }
24 |
--------------------------------------------------------------------------------
/app/styles/themes.json:
--------------------------------------------------------------------------------
1 | {
2 | "default": "Default theme",
3 | "dark": "Dark"
4 | }
5 |
--------------------------------------------------------------------------------
/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Laverna
4 |
5 | Open source note taking application
6 |
7 |
8 | Laverna project
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/electron/electron.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron');
2 |
3 | // TODO: reference the config file for this.
4 | port = 9000;
5 |
6 | const {app, BrowserWindow} = require('electron');
7 | const laverna = require('./server.js')(port);
8 | //const {sigApp, sigServer} = require('./laverna-server/server.js');
9 |
10 | //laverna.on("listening", function () { laverna.close(); });
11 |
12 | function createWindow () {
13 | let win = new BrowserWindow({
14 | width: 925,
15 | height: 800,
16 | backgroundColor: '#259D7A',
17 | minWidth: 925,
18 | // everything was working fine without the below line. Suddenly we needed it.
19 | // I cannot for the life of me figure out why.
20 | webPreferences: { nodeIntegration: false }
21 | });
22 | //win.loadFile('dist/index.html');
23 | win.loadURL("http://localhost:" + port);
24 | win.webContents.openDevTools();
25 | // Emitted when the window is closed.
26 | win.on('closed', () => {
27 | // Dereference the window object, usually you would store windows
28 | // in an array if your app supports multi windows, this is the time
29 | // when you should delete the corresponding element.
30 | laverna.close();
31 | //sigServer.close();
32 | //sigApp.quit();
33 | win = null;
34 | });
35 | }
36 |
37 | app.on('ready', createWindow);
38 | app.on('window-all-closed', () => {
39 | app.quit();
40 | });
41 |
--------------------------------------------------------------------------------
/electron/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laverna",
3 | "version": "1.0.0-beta.3",
4 | "description": "",
5 | "main": "electron.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "finalhandler": "^1.1.1",
14 | "serve-static": "^1.13.2"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/electron/server.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 |
4 | var finalhandler = require('finalhandler'),
5 | http = require('http'),
6 | serveStatic = require('serve-static'),
7 | serve,
8 | server;
9 |
10 | serve = serveStatic(__dirname + '/dist', {index: ['index.html']});
11 |
12 | server = http.createServer(function(req, res) {
13 | var done = finalhandler(req, res);
14 | serve(req, res, done);
15 | });
16 |
17 | module.exports = function(port) {
18 | console.log('Server is running on port: ' + port);
19 | return server.listen(port);
20 | };
--------------------------------------------------------------------------------
/gulps/bundle.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const webpackStream = require('webpack-stream');
4 | const webpack = require('webpack');
5 |
6 | /**
7 | * @file Bundle JS files.
8 | * @example gulp bundle // Bundle JS files
9 | * @example gulp bundle --prod // Bundle JS for production (minify, uglify)
10 | * @returns {Function}
11 | */
12 | module.exports = (gulp, $) => {
13 |
14 | // Use a different config file for production
15 | let webpackConfig = ($.util.env.prod ? 'production.config' : 'config.js');
16 | webpackConfig = require(`../webpack.${webpackConfig}`);
17 |
18 | return cb => {
19 | const isBuild = process.argv[2] === 'build';
20 | let called = false;
21 |
22 | // Watch for changes unless we're building the project
23 | if (!isBuild) {
24 | webpackConfig.watch = true;
25 | }
26 |
27 | return gulp.src('app/scripts/main.js')
28 | .pipe(webpackStream(webpackConfig, webpack, () => {
29 | $.browserSync.stream();
30 |
31 | if (!called && !isBuild) {
32 | called = true;
33 | cb();
34 | }
35 | }))
36 | .pipe(gulp.dest(`${$.distDir}/scripts/`))
37 | .pipe($.browserSync.stream());
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/gulps/clean.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @file Tasks for cleaning up.
5 | * @example gulp clean:dist // Clean dist directory
6 | * @example gulp clean:release // Clean release directory
7 | */
8 | module.exports = function(gulp, $) {
9 |
10 | gulp.task('clean:dist', () => $.del([`${$.distDir}/**/*`]));
11 |
12 | gulp.task('clean:release', () => $.del(['./release/*']));
13 |
14 | };
15 |
--------------------------------------------------------------------------------
/gulps/copy.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const merge = require('merge-stream');
3 |
4 | /**
5 | * @file Tasks for copying static files to dist directory.
6 | * @example gulp copy // Copy static files
7 | */
8 | module.exports = (gulp, $) => {
9 | return () => {
10 | return merge.apply(merge, [
11 | gulp.src([
12 | './LICENSE',
13 | ], {base: './'})
14 | .pipe(gulp.dest($.distDir)),
15 |
16 | // Copy static files like images, locales, etc...
17 | gulp.src([
18 | './app/images/**/*.+(png|jpg|gif|ico|icns)',
19 | './app/docs/**',
20 | './app/locales/**/*.json',
21 | './app/.htaccess',
22 | './app/*.+(xml|ico|txt|webapp)',
23 | './app/styles/**/*.+(eot|svg|ttf|woff)',
24 | ], {base: './app'})
25 | .pipe(gulp.dest($.distDir)),
26 |
27 | gulp.src([
28 | './node_modules/openpgp/dist/openpgp.worker.js',
29 | './node_modules/openpgp/dist/openpgp.js',
30 | ]).pipe(gulp.dest(`${$.distDir}/scripts`)),
31 | ]);
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/gulps/copyDist.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(gulp) {
4 | return function() {
5 | return gulp.src([
6 | './dist/**',
7 | ], {base: './'})
8 | .pipe(gulp.dest('./release/laverna/'));
9 | };
10 | };
11 |
12 |
--------------------------------------------------------------------------------
/gulps/copyRelease.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(gulp, plugins) {
4 | return function() {
5 | /*return gulp.src([
6 | './preload.js',
7 | './server.js',
8 | './electron.js',
9 | './package-lock.json',
10 | './npm-shrinkwrap.json',
11 | './package.json',
12 | ], {base: './'})
13 | .pipe(gulp.dest('./release/laverna'));*/
14 | return gulp.src([
15 | './release',
16 | ])
17 | .pipe(plugins.shell(
18 | 'cp ./electron/electron.js ./electron/package-lock.json ./electron/package.json ./electron/server.js release/laverna'
19 | ));
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/gulps/css.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | /**
6 | * @file Create CSS related Gulp tasks.
7 | *
8 | * @example gulp css // bundle CSS files
9 | * @example gulp cssmin // minify CSS for production
10 | */
11 | module.exports = (gulp, $) => {
12 |
13 | gulp.task('css', () => {
14 | return gulp.src('./app/styles/*/main.less')
15 | .pipe($.less({
16 | paths: [
17 | path.join(__dirname, 'less', 'includes'),
18 | './node_modules/',
19 | ],
20 | }))
21 | // Unique names for theme files
22 | .pipe($.rename(pathF => {
23 | const pathNew = pathF;
24 | pathNew.basename = pathNew.dirname;
25 | pathNew.dirname = '';
26 | return pathNew;
27 | }))
28 | // Autoprefixer
29 | .pipe($.autoprefixer({
30 | browsers : ['> 5%'],
31 | cascade : false,
32 | remove : false,
33 | add : true,
34 | }))
35 | .pipe(gulp.dest(`${$.distDir}/styles/`))
36 | .pipe($.browserSync.stream());
37 | });
38 |
39 | gulp.task('cssmin', () => {
40 | return gulp.src(`${$.distDir}/styles/*.css`)
41 | .pipe($.cleanCss({
42 | compatibility: 'ie8',
43 | }))
44 | .pipe(gulp.dest(`${$.distDir}/styles`));
45 | });
46 |
47 | };
48 |
--------------------------------------------------------------------------------
/gulps/lav-server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(gulp, plugins) {
4 |
5 | gulp.task('lav-server', function() {
6 | return gulp.src('./release')
7 | .pipe(plugins.shell(
8 | 'cd ./release/laverna && curl -o laverna-server.tar.gz https://codeload.github.com/daed/laverna-server/tar.gz/server-1.0.0 && gzip -d laverna-server.tar.gz && tar xvf laverna-server.tar && rm -rf laverna-server.tar && mv laverna-server-server-1.0.0 laverna-server && cd ../'
9 | ));
10 | });
11 |
12 | };
13 |
--------------------------------------------------------------------------------
/gulps/lint.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @file Lint tasks.
5 | *
6 | * @example gulp eslint // JavaScript linter
7 | * @example gulp jsonlint // JSON linter
8 | * @example gulp lint // Run all linters
9 | */
10 | module.exports = (gulp, $) => {
11 | gulp.task('eslint', $.shell.task(['npm run eslint']));
12 |
13 | gulp.task('jsonlint', () => {
14 | return gulp.src([
15 | 'app/manifest.webapp',
16 | 'bower.json',
17 | 'package.json',
18 | 'app/**/*.json',
19 | ])
20 | .pipe($.jsonlint())
21 | .pipe($.jsonlint.failAfterError())
22 | .pipe($.jsonlint.reporter());
23 | });
24 |
25 | gulp.task('lint', ['jsonlint', 'eslint']);
26 | };
27 |
--------------------------------------------------------------------------------
/gulps/nightwatch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Run UI tests.
5 | * Example of running tests:
6 | * gulp nightwatch --env [test_settings profile]
7 | */
8 | module.exports = function(gulp, plugins) {
9 | return function() {
10 | gulp.task('nightwatch', function() {
11 | return gulp.src('./test/spec-ui/test.js', {read: false})
12 | .pipe(plugins.nightwatch({
13 | configFile : './test/nightwatch.json',
14 | cliArgs : [
15 | // '--test ' + './test/spec-ui/tests/apps/notes/show.js',
16 | '--env ' + (plugins.util.env.env || 'default')
17 | ]
18 | }))
19 | .once('error', function(err) {
20 | console.log('Error', err.toString());
21 | process.exit(1);
22 | });
23 | });
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/gulps/npm.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(gulp, plugins) {
4 |
5 | gulp.task('npm:install', function() {
6 | return gulp.src('./release/laverna/package.json')
7 | .pipe(plugins.shell(
8 | 'cd ./release/laverna && npm install --production && cd ../../'
9 | ));
10 | });
11 |
12 | };
13 |
--------------------------------------------------------------------------------
/gulps/serve-hosted.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @file Live reload server.
5 | *
6 | * @example gulp serve // Starts live-reload server
7 | */
8 | module.exports = (gulp, $) => {
9 | return () => {
10 | $.browserSync.init({
11 | notify : false,
12 | open : !$.util.env.dev,
13 | server : $.util.env.root || $.distDir,
14 | port : $.util.env.port || 9001,
15 | ghostMode : $.util.env.ghostMode !== undefined,
16 | });
17 |
18 | // Watch for changes in SASS and HTML
19 | gulp.watch('app/styles/**/*.less', ['css']);
20 | gulp.watch('app/*.html', ['html']);
21 |
22 | // Re-bundle if some new packages were installed
23 | gulp.watch('package.json', ['bundle']);
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/gulps/serve.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @file Live reload server.
5 | *
6 | * @example gulp serve // Starts live-reload server
7 | */
8 | module.exports = (gulp, $) => {
9 | return () => {
10 | $.browserSync.init({
11 | notify : false,
12 | open : !$.util.env.dev,
13 | server : $.util.env.root || $.distDir,
14 | port : $.util.env.port || 9000,
15 | ghostMode : $.util.env.ghostMode !== undefined,
16 | });
17 |
18 | // Watch for changes in SASS and HTML
19 | gulp.watch('app/styles/**/*.less', ['css']);
20 | gulp.watch('app/*.html', ['html']);
21 |
22 | // Re-bundle if some new packages were installed
23 | gulp.watch('package.json', ['bundle']);
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/gulps/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @file Test related tasks.
5 | * @example gulp tape // Run Unit tests once
6 | * @example gulp test:run // Run Unit tests and linters once
7 | * @example gulp test // Run Unit tests and watch for changes
8 | * @example gulp cover:run // Generate coverage report once
9 | * @example gulp cover // Generate coverage report and watch for changes
10 | * @todo enable lint tests
11 | */
12 | module.exports = function(gulp, $) {
13 |
14 | gulp.task('tape', $.shell.task([
15 | 'babel-node ./test/tape/index.js | faucet',
16 | ]));
17 |
18 | gulp.task('tape:debug', $.shell.task([
19 | 'set TAP_DIAG=1 && babel-node ./test/tape/index.js',
20 | ]));
21 |
22 | gulp.task('test:run', ['lint', 'tape']);
23 |
24 | gulp.task('test', ['test:run'], () => {
25 | gulp.watch(['app/scripts/**/*.js', 'test/tape/**/*.js'], ['tape']);
26 | });
27 |
28 | gulp.task('cover:run', $.shell.task(['npm run cover']));
29 | gulp.task('cover', ['cover:run'], () => {
30 | gulp.watch(['app/scripts/**/*.js', 'test/tape/**/*.js'], ['cover:run']);
31 | });
32 |
33 | };
34 |
--------------------------------------------------------------------------------
/test/spec-ui/commands/addNote.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | exports.command = function(item) {
3 | this
4 | .urlHash('notes/add')
5 | .expect.element('#editor--input--title').to.be.visible.before(50000);
6 |
7 | this
8 | .setValue('#editor--input--title', item.title)
9 | .expect.element('#editor--input--title').value.to.contain(item.title).before(2000);
10 |
11 | this
12 | .click('.CodeMirror-lines')
13 | .keys(item.content);
14 |
15 | if (item.notebook) {
16 | this
17 | .click('.addNotebook')
18 | .expect.element('.modal--input[name=name]').to.be.visible.before(1000);
19 |
20 | this
21 | .setValue('.modal--input[name=name]', [item.notebook, this.Keys.ENTER])
22 | .pause(1000);
23 | }
24 |
25 | this
26 | .click('.editor--save')
27 | .expect.element('.layout--body.-form').not.to.be.present.before(2000);
28 |
29 | return this;
30 | };
31 |
--------------------------------------------------------------------------------
/test/spec-ui/commands/addNotebook.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.command = function(item) {
4 | this
5 | .urlHash('notebooks/add')
6 | .expect.element('#modal input[name="name"]').to.be.present.before(5000);
7 |
8 | this.expect.element('#modal input[name="name"]').to.be.visible.before(5000);
9 | // this.expect.element('#modal .form-group').to.be.visible.before(5000);
10 |
11 | this.setValue('#modal input[name="name"]', item.name);
12 |
13 | this.perform(function(client, done) {
14 | client.execute(function(filter) {
15 | var ops = document.querySelectorAll('#modal select[name="parentId"] option');
16 | for (var i = 0, len = ops.length; i < len; i++) {
17 | if (filter && ops[i].text.indexOf(filter) > -1) {
18 | document
19 | .querySelector('#modal select[name="parentId"]')
20 | .selectedIndex = ops[i].index;
21 | }
22 | }
23 | }, [item.parentId], function() {
24 | done();
25 | });
26 | });
27 |
28 | this.keys(this.Keys.ENTER);
29 | this.expect.element('#modal input[name="name"]').to.be.not.present.before(5000);
30 |
31 | return this;
32 | };
33 |
--------------------------------------------------------------------------------
/test/spec-ui/commands/addTag.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.command = function(item) {
4 | this
5 | .urlHash('notebooks')
6 | .pause(100)
7 | .urlHash('tags/add')
8 | .expect.element('#modal .form-group').to.be.present.before(5000);
9 |
10 | // this.expect.element('#modal .form-group').to.be.visible.before(5000);
11 |
12 | this.setValue('#modal input[name="name"]', [item.name, this.Keys.ENTER]);
13 |
14 | return this;
15 | };
16 |
--------------------------------------------------------------------------------
/test/spec-ui/commands/changeEncryption.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * This Nightwatch command changes encryption settings.
4 | */
5 | exports.command = function(data) {
6 | this
7 | .urlHash('settings/encryption')
8 | .expect.element('.-tab-encryption').to.be.present.before(5000);
9 |
10 | this.getAttribute('input[name=encrypt]', 'checked', function(res) {
11 | if (data.use && res.value === null) {
12 | this.click('input[name="encrypt"]');
13 | }
14 | });
15 |
16 | this
17 | .clearValue('input[name="encryptPass"]')
18 | .setValue('input[name="encryptPass"]', data.password)
19 | .click('#randomize')
20 | .click('.settings--save')
21 | .pause(1000)
22 | .click('.settings--cancel');
23 |
24 | return this;
25 | };
26 |
--------------------------------------------------------------------------------
/test/spec-ui/commands/closeWelcome.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.command = function() {
4 | this
5 | .urlHash('/notes')
6 | .expect.element('#welcome--page').to.be.present.before(100000);
7 |
8 | this
9 | .expect.element('.modal-header .close').to.be.present.before(5000);
10 |
11 | this
12 | .click('.modal-header .close')
13 | .keys(this.Keys.ESCAPE)
14 | .expect.element('#welcome--page').not.to.be.present.before(10000);
15 |
16 | return this;
17 | };
18 |
--------------------------------------------------------------------------------
/test/spec-ui/commands/findAll.js:
--------------------------------------------------------------------------------
1 | exports.command = function(selector, attr, callback) {
2 | this.execute(function(selector, attr) {
3 | var els = document.querySelectorAll(selector),
4 | param = [];
5 |
6 | for (var i = 0, len = els.length; i < len; i++) {
7 | if (attr) {
8 | param.push(els[i].getAttribute(attr));
9 | } else {
10 | param.push(els[i]);
11 | }
12 | }
13 |
14 | return param;
15 | }, [selector, attr], function(res) {
16 | callback(res.value);
17 | });
18 |
19 | return this;
20 | };
21 |
--------------------------------------------------------------------------------
/test/spec-ui/modules/remotestorage/auth.js:
--------------------------------------------------------------------------------
1 | /* global it */
2 | 'use strict';
3 |
4 | /**
5 | * It tests if it's possible to login to a RemoteStorage server.
6 | */
7 | it('creates a new user on RemoteStorage server', function(client) {
8 | client
9 | .url('http://localhost:9100/signup')
10 | .expect.element('input[type="password"]').to.be.present.before(50000);
11 |
12 | client
13 | .setValue('#username', 'test')
14 | .setValue('#email', 'test@example.com')
15 | .setValue('#password', ['1', client.Keys.ENTER])
16 | .pause(300);
17 | });
18 |
19 | it('shows RemoteStorage widget', function(client) {
20 | client
21 | .urlHash('notes')
22 | .expect.element('.remotestorage-initial').to.be.present.before(50000);
23 | });
24 |
25 | it('can login to a RemoteStorage server', function(client) {
26 | client
27 | .click('.rs-bubble')
28 | .setValue('.remotestorage-initial input[name="userAddress"]', 'test@localhost:9100')
29 | .click('.remotestorage-initial .connect')
30 | .expect.element('input[type="password"]').to.be.present.before(10000);
31 |
32 | client.assert.urlContains('localhost:9100');
33 |
34 | client
35 | .setValue('#password', ['1', client.Keys.ENTER])
36 | .expect.element('.remotestorage-connected').to.be.present.before(50000);
37 | });
38 |
--------------------------------------------------------------------------------
/test/spec-ui/modules/remotestorage/client2.js:
--------------------------------------------------------------------------------
1 | /* global describe, before, after, it */
2 | 'use strict';
3 |
4 | describe('RemoteStorage: client 2', function() {
5 |
6 | before(function(client, done) {
7 | done();
8 | });
9 |
10 | after(function(client, done) {
11 | client.end(function() {
12 | done();
13 | });
14 | });
15 |
16 | it('wait', function(client) {
17 | client
18 | .urlHash('notes')
19 | .expect.element('.list').to.be.present.before(50000);
20 | });
21 |
22 | it('creates new data', function(client) {
23 | client
24 | .addNote({title: 'Note from client 2'})
25 | .pause(500)
26 | .addNotebook({name: 'Notebook from client 2'})
27 | .pause(500)
28 | .addTag({name: 'Tag from client 2'});
29 | });
30 |
31 | // Try to login to a RemoteStorage first
32 | require('./auth.js');
33 |
34 | it('fetches notes from Remotestorage', function(client) {
35 | client.urlHash('notes');
36 | client.expect.element('#header--add').to.be.present.before(50000);
37 |
38 | client.expect
39 | .element('#sidebar--content').to.have.text.that.contains('Note from client 1')
40 | .before(50000);
41 | });
42 |
43 | it('fetches notebooks & tags from Remotestorage', function(client) {
44 | client.urlHash('notebooks');
45 |
46 | client.expect
47 | .element('#notebooks').text.to.contain('Notebook from client 1')
48 | .before(50000);
49 |
50 | client.expect
51 | .element('#tags').text.to.contain('Tag from client 1')
52 | .that.contains('Tag from client 1')
53 | .before(50000);
54 | });
55 |
56 | });
57 |
--------------------------------------------------------------------------------
/test/spec-ui/tests/apps/settings/import.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Notebook list test
5 | // |)}>#
6 | module.exports = {
7 |
8 | before: function(client) {
9 | client.closeWelcome();
10 |
11 | client.urlHash('settings/importExport');
12 | client.expect.element('.header--title').to.have.text.that.contains('Settings').before(50000);
13 | },
14 |
15 | after: function(client) {
16 | client.end();
17 | },
18 |
19 | '"Import & Export" tab is active in #/settings/importExports': function(client) {
20 | client
21 | .expect.element('.list--settings.active').to.have.text.that.contains('Import & Export');
22 | },
23 |
24 | // @TODO Test importing & exporting json config files
25 | };
26 | */
27 |
--------------------------------------------------------------------------------
/test/spec-ui/tests/apps/tags/remove.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Tag removal test
5 | */
6 | module.exports = {
7 |
8 | before: function(client) {
9 | client.closeWelcome();
10 |
11 | client.urlHash('notes');
12 | client.expect.element('#header--add').to.be.visible.before(50000);
13 | client.urlHash('notebooks');
14 | },
15 |
16 | after: function(client) {
17 | client.end();
18 | },
19 |
20 | 'can remove a tag': function(client) {
21 | // Prepare a tag to delete
22 | client.addTag({name: '1.ToRemove'});
23 |
24 | client
25 | .urlHash('notes')
26 | .pause(100)
27 | .urlHash('notebooks');
28 |
29 | client.expect.element('#tags').text.to.contain('1.ToRemove').before(5000);
30 |
31 | // Delete the tag
32 | client
33 | .click('#tags .list--buttons .drop-edit')
34 | .click('#tags .list--buttons .remove-link');
35 |
36 | client.expect.element('#modal .modal-title').to.be.present.before(5000);
37 | client.expect.element('#modal .modal-title').to.be.visible.before(5000);
38 | client.click('#modal .btn[data-event="confirm"]');
39 | client.pause(500);
40 |
41 | client.expect.element('#tags').text.not.to.contain('1.ToRemove').before(5000);
42 | },
43 |
44 | 'deleted tags don\'t re-appear after url change': function(client) {
45 | client
46 | .urlHash('notes')
47 | .pause(1000)
48 | .urlHash('notebooks');
49 |
50 | client
51 | .expect.element('#tags').text.not.to.contain('1.ToRemove')
52 | .before(5000);
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/test/tape/behaviors/modelFocus.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test behaviors/ModelFocus.js
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 |
8 | import ModelFocus from '../../../app/scripts/behaviors/ModelFocus';
9 |
10 | let sand;
11 | test('behaviors/ModelFocus: before()', t => {
12 | sand = sinon.sandbox.create();
13 | t.end();
14 | });
15 |
16 | test('behaviors/ModelFocus: ui()', t => {
17 | const ui = ModelFocus.prototype.ui();
18 | t.equal(typeof ui, 'object', 'returns an object');
19 | t.equal(ui.listGroup, '.list-group-item:first');
20 |
21 | t.end();
22 | });
23 |
24 | test('behaviors/ModelFocus: modelEvents()', t => {
25 | const events = ModelFocus.prototype.modelEvents();
26 | t.equal(typeof events, 'object', 'returns an object');
27 | t.equal(events.focus, 'onFocus');
28 |
29 | t.end();
30 | });
31 |
32 | test('behaviors/ModelFocus: onFocus()', t => {
33 | const focus = new ModelFocus();
34 | focus.view = {trigger: sand.stub()};
35 | focus.oldUi = focus.ui;
36 | focus.ui.listGroup = {
37 | addClass : sand.stub(),
38 | offset : sand.stub().returns(1),
39 | };
40 |
41 | focus.onFocus();
42 | t.equal(focus.ui.listGroup.addClass.calledWith('active'), true,
43 | 'adds "active" class to listGroup element');
44 | t.equal(focus.view.trigger.calledWith('scroll:top', {offset: 1}), true,
45 | 'triggers "scroll:top" event');
46 |
47 | focus.view = null;
48 | focus.ui = focus.oldUi;
49 | sand.restore();
50 | t.end();
51 | });
52 |
--------------------------------------------------------------------------------
/test/tape/behaviors/sidebar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test behaviors/Sidebar
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 |
9 | import Sidebar from '../../../app/scripts/behaviors/Sidebar';
10 |
11 | let sand;
12 | test('behaviors/Sidebar: before()', t => {
13 | sand = sinon.sandbox.create();
14 | t.end();
15 | });
16 |
17 | test('behaviors/Sidebar: onDestroy()', t => {
18 | const side = new Sidebar();
19 | side.hammer = {destroy: sand.stub()};
20 |
21 | side.onDestroy();
22 | t.equal(side.hammer.destroy.called, true, 'destroyes the hammer instance');
23 |
24 | sand.restore();
25 | t.end();
26 | });
27 |
28 | test('behaviors/Sidebar: onSwipeRight()', t => {
29 | const side = new Sidebar();
30 | const trig = sand.stub(Radio, 'trigger');
31 |
32 | side.onSwipeRight();
33 | t.equal(trig.calledWith('components/navbar', 'show:sidemenu'), true,
34 | 'shows the sidebar menu');
35 |
36 | sand.restore();
37 | t.end();
38 | });
39 |
40 | test('behaviors/Sidebar: onSwipeLeft()', t => {
41 | const side = new Sidebar();
42 | const req = sand.stub(Radio, 'request');
43 | side.view = {noSwipeLeft: true};
44 |
45 | side.onSwipeLeft();
46 | t.equal(req.notCalled, true,
47 | 'does nothing if the noSwipeRight property is equal to true');
48 |
49 | side.view.noSwipeLeft = false;
50 | side.onSwipeLeft();
51 | t.equal(req.calledWith('Layout', 'toggleContent', {
52 | visible: true,
53 | }), true, 'shows content region and hides the sidebar');
54 |
55 | sand.restore();
56 | t.end();
57 | });
58 |
--------------------------------------------------------------------------------
/test/tape/collections/files.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Test collections/Files
3 | */
4 | import test from 'tape';
5 | import Files from '../../../app/scripts/collections/Files';
6 | import File from '../../../app/scripts/models/File';
7 |
8 | test('collections/Files: sync', t => {
9 | const coll = new Files();
10 | t.equal(typeof coll.sync, 'function', 'has sync method');
11 | t.end();
12 | });
13 |
14 | test('collections/Files: profileId', t => {
15 | t.equal(new Files().profileId, undefined, 'is undefined by default');
16 | t.equal(new Files(null, {profileId: 'test'}).profileId, 'test');
17 | t.end();
18 | });
19 |
20 | test('collections/Files: model', t => {
21 | const coll = new Files();
22 | t.equal(coll.model, File, 'uses file model');
23 | t.end();
24 | });
25 |
--------------------------------------------------------------------------------
/test/tape/collections/modules/edits.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Test collections/modules/Edits
3 | */
4 | import test from 'tape';
5 | import sinon from 'sinon';
6 |
7 | import '../../../../app/scripts/utils/underscore';
8 | import Module from '../../../../app/scripts/collections/modules/Edits';
9 | import Edits from '../../../../app/scripts/collections/Edits';
10 |
11 | import Edit from '../../../../app/scripts/models/Edit';
12 | import Shadow from '../../../../app/scripts/models/Shadow';
13 |
14 | let sand;
15 | test('collections/modules/Edits: before()', t => {
16 | sand = sinon.sandbox.create();
17 | t.end();
18 | });
19 |
20 | test('collections/modules/Edits: Collection', t => {
21 | const mod = new Module();
22 | t.equal(mod.Collection, Edits);
23 | t.end();
24 | });
25 |
26 | test('collections/modules/Edits: constructor()', t => {
27 | const reply = sand.stub(Module.prototype.channel, 'reply');
28 | const mod = new Module();
29 |
30 | t.equal(reply.calledWith({
31 | }), true, 'replies to additional requests');
32 |
33 | sand.restore();
34 | t.end();
35 | });
36 |
--------------------------------------------------------------------------------
/test/tape/collections/modules/shadows.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Test collections/modules/Shadows
3 | */
4 | import test from 'tape';
5 | import proxyquire from 'proxyquire';
6 | import sinon from 'sinon';
7 |
8 | import Module from '../../../../app/scripts/collections/modules/Shadows';
9 | import Shadows from '../../../../app/scripts/collections/Shadows';
10 |
11 | let sand;
12 | test('collections/modules/Shadows: before()', t => {
13 | sand = sinon.sandbox.create();
14 | t.end();
15 | });
16 |
17 | test('collections/modules/Shadows: Collection', t => {
18 | const mod = new Module();
19 | t.equal(mod.Collection, Shadows);
20 | t.end();
21 | });
22 |
--------------------------------------------------------------------------------
/test/tape/collections/profiles.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Test collections/Profiles
3 | */
4 | import test from 'tape';
5 | import '../../../app/scripts/utils/underscore';
6 | import Profiles from '../../../app/scripts/collections/Profiles';
7 | import Profile from '../../../app/scripts/models/Profile';
8 |
9 | test('collections/Profiles: model', t => {
10 | t.equal(Profiles.prototype.model, Profile);
11 | t.end();
12 | });
13 |
14 | test('collections/Profiles: constructor()', t => {
15 | const prof = new Profiles();
16 | t.equal(prof.profileId, 'default', 'profileId is always equal to "default"');
17 | t.end();
18 | });
19 |
--------------------------------------------------------------------------------
/test/tape/components/dropbox/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Test components/dropbox/main
3 | */
4 | import test from 'tape';
5 | import sinon from 'sinon';
6 | import Radio from 'backbone.radio';
7 |
8 | import '../../../../app/scripts/utils/underscore';
9 | import initialize from '../../../../app/scripts/components/dropbox/main';
10 | import Sync from '../../../../app/scripts/components/dropbox/Sync';
11 | import View from '../../../../app/scripts/components/dropbox/settings/View';
12 |
13 | let sand;
14 | test('components/dropbox/main: before()', t => {
15 | sand = sinon.sandbox.create();
16 | t.end();
17 | });
18 |
19 | test('components/dropbox/main', t => {
20 | const stub = sand.stub(Sync.prototype, 'init').resolves();
21 |
22 | initialize();
23 | t.equal(stub.notCalled, true, 'does not initialize Sync.js');
24 |
25 | sand.stub(Radio, 'request')
26 | .withArgs('collections/Configs', 'findConfigs')
27 | .returns({dropboxKey: ''})
28 | .withArgs('collections/Configs', 'findConfig')
29 | .returns('dropbox');
30 |
31 | initialize();
32 | t.equal(stub.called, true, 'msg');
33 |
34 | sand.restore();
35 | t.equal(Radio.request('components/dropbox', 'getSettingsView'), View,
36 | 'replies with the settings view');
37 |
38 | sand.restore();
39 | t.end();
40 | });
41 |
--------------------------------------------------------------------------------
/test/tape/components/dropbox/settings/view.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Test components/dropbox/settings/View
3 | */
4 | import test from 'tape';
5 | import sinon from 'sinon';
6 |
7 | import _ from '../../../../../app/scripts/utils/underscore';
8 | import '../../../../../app/scripts/utils/underscore';
9 | import Configs from '../../../../../app/scripts/collections/Configs';
10 | import {configNames} from '../../../../../app/scripts/collections/configNames';
11 | import View from '../../../../../app/scripts/components/dropbox/settings/View';
12 |
13 | let sand;
14 | test('components/dropbox/settings/View: before()', t => {
15 | sand = sinon.sandbox.create();
16 | t.end();
17 | });
18 |
19 | test('components/dropbox/settings/View: serializeData()', t => {
20 | const collection = new Configs();
21 |
22 | //collection.resetFromObject(collection.configNames);
23 |
24 | const models = [];
25 | _.each(configNames, (value, name) => models.push({name, value}));
26 | collection.reset(models);
27 |
28 | const view = new View({collection});
29 |
30 | const data = view.serializeData();
31 | t.equal(data.dropboxKey, '', 'is empty string');
32 | t.equal(data.placeholder, 'Optional', 'custom Dropbox API key is not needed');
33 |
34 | Object.defineProperty(view, 'dropboxKeyNeed', {get: () => true});
35 | collection.get('dropboxKey').set('value', '1234');
36 | const data2 = view.serializeData();
37 | t.equal(data2.dropboxKey, '1234', 'is equal to the current value');
38 | t.equal(data2.placeholder, 'Required', 'custom Dropbox API key is required');
39 |
40 | sand.restore();
41 | t.end();
42 | });
43 |
--------------------------------------------------------------------------------
/test/tape/components/encryption/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/encryption/main
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 | import '../../../../app/scripts/utils/underscore';
9 |
10 | import initialize from '../../../../app/scripts/components/encryption/main';
11 | import Auth from '../../../../app/scripts/components/encryption/auth/Controller';
12 | import Encrypt from '../../../../app/scripts/components/encryption/encrypt/Controller';
13 |
14 | let sand;
15 | test('encryption/main: before()', t => {
16 | sand = sinon.sandbox.create();
17 | t.end();
18 | });
19 |
20 | test('encryption/Main', t => {
21 | const authInit = sand.stub(Auth.prototype, 'init');
22 | const encryptInit = sand.stub(Encrypt.prototype, 'init');
23 | const req = sand.stub(Radio, 'request').callsFake((n, m, data) => data.callback());
24 |
25 | initialize();
26 | t.equal(req.callCount, 2, 'adds two initializer');
27 | t.equal(req.calledWithMatch('utils/Initializer', 'add', {name: 'App:auth'}), true,
28 | 'adds App:auth initializer');
29 | t.equal(authInit.called, true, 'instantiates "auth" controller');
30 | t.equal(encryptInit.called, true, 'instantiates "auth" controller');
31 |
32 | sand.restore();
33 | t.end();
34 | });
35 |
--------------------------------------------------------------------------------
/test/tape/components/fileDialog/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/fileDialog/main
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 | import '../../../../app/scripts/utils/underscore';
9 |
10 | import initialize from '../../../../app/scripts/components/fileDialog/main';
11 |
12 | test('fileDialog/main', t => {
13 | const reply = sinon.stub();
14 | sinon.stub(Radio, 'channel').returns({reply});
15 |
16 | initialize();
17 | t.equal(reply.calledWith('show'), true, 'replies to "show" request');
18 |
19 | Radio.channel.restore();
20 | t.end();
21 | });
22 |
--------------------------------------------------------------------------------
/test/tape/components/fuzzySearch/child.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/fuzzySearch/views/Child
3 | * @file
4 | */
5 | import test from 'tape';
6 | import '../../../../app/scripts/utils/underscore';
7 |
8 | import Child from '../../../../app/scripts/components/fuzzySearch/views/Child';
9 |
10 | test('fuzzySearch/views/Child: className', t => {
11 | t.equal(Child.prototype.className, 'list-group list--group');
12 | t.end();
13 | });
14 |
15 | test('fuzzySearch/views/Child: triggers()', t => {
16 | const view = new Child();
17 | const trig = view.triggers();
18 |
19 | t.equal(typeof trig, 'object', 'returns an object');
20 | t.equal(trig['click .list-group-item'], 'navigate:search',
21 | 'triggers navigate:search event');
22 |
23 | t.end();
24 | });
25 |
--------------------------------------------------------------------------------
/test/tape/components/fuzzySearch/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/fuzzySearch/main
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 | import '../../../../app/scripts/utils/underscore';
9 |
10 | import main from '../../../../app/scripts/components/fuzzySearch/main';
11 | import regionClass from '../../../../app/scripts/components/fuzzySearch/views/Region';
12 |
13 | let sand;
14 | test('fuzzySearch/main: before()', t => {
15 | sand = sinon.sandbox.create();
16 | t.end();
17 | });
18 |
19 | test('fuzzySearch/main: initialize()', t => {
20 | const on = sand.stub();
21 | sand.stub(Radio, 'channel').withArgs('components/navbar')
22 | .returns({on});
23 | sand.stub(main, 'createRegion');
24 |
25 | main.initialize();
26 | t.equal(main.createRegion.called, true, 'creates fuzzySearch region');
27 | t.equal(on.calledWith('shown:search'), true, 'listens to shown:search event');
28 |
29 | sand.restore();
30 | t.end();
31 | });
32 |
33 | test('fuzzySearch/main: createRegion()', t => {
34 | const req = sand.stub(Radio, 'request');
35 |
36 | main.createRegion();
37 | t.equal(req.calledWith('Layout', 'add', {
38 | region : 'fuzzySearch',
39 | regionOptions : {regionClass, el: '#sidebar--fuzzy'},
40 | }), true, 'creates fuzzySearch region');
41 |
42 | sand.restore();
43 | t.end();
44 | });
45 |
--------------------------------------------------------------------------------
/test/tape/components/fuzzySearch/region.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/fuzzySearch/views/Region
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import '../../../../app/scripts/utils/underscore';
8 |
9 | import Region from '../../../../app/scripts/components/fuzzySearch/views/Region';
10 |
11 | let sand;
12 | test('fuzzySearch/views/Region: before()', t => {
13 | sand = sinon.sandbox.create();
14 | t.end();
15 | });
16 |
17 | test('fuzzySearch/views/Region: onShow()', t => {
18 | const prot = Region.prototype;
19 | prot.$body = {addClass : sand.stub()};
20 | prot.$el = {removeClass: sand.stub()};
21 |
22 | prot.onShow();
23 | t.equal(prot.$body.addClass.calledWith('-fuzzy'), true,
24 | 'adds -fuzzy class to the body');
25 | t.equal(prot.$el.removeClass.calledWith('hidden'), true,
26 | 'shows the region element');
27 |
28 | prot.$body = null;
29 | prot.$el = null;
30 | sand.restore();
31 | t.end();
32 | });
33 |
34 | test('fuzzySearch/views/Region: onEmpty()', t => {
35 | const prot = Region.prototype;
36 | prot.$body = {removeClass: sand.stub()};
37 | prot.$el = {addClass : sand.stub()};
38 |
39 | prot.onEmpty();
40 | t.equal(prot.$el.addClass.calledWith('hidden'), true,
41 | 'hides the region element');
42 | t.equal(prot.$body.removeClass.calledWith('-fuzzy'), true,
43 | 'removes -fuzzy class from the body');
44 |
45 | prot.$body = null;
46 | prot.$el = null;
47 | sand.restore();
48 | t.end();
49 | });
50 |
--------------------------------------------------------------------------------
/test/tape/components/fuzzySearch/view.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/fuzzySearch/views/View
3 | * @file
4 | */
5 | import test from 'tape';
6 | import '../../../../app/scripts/utils/underscore';
7 |
8 | import View from '../../../../app/scripts/components/fuzzySearch/views/View';
9 | import Child from '../../../../app/scripts/components/fuzzySearch/views/Child';
10 |
11 | test('View: className', t => {
12 | t.equal(View.prototype.className, 'main notes-list');
13 | t.end();
14 | });
15 |
16 | test('View: childViewContainer', t => {
17 | t.equal(View.prototype.childViewContainer, '.notes-list');
18 | t.end();
19 | });
20 |
21 | test('View: childView', t => {
22 | t.equal(View.prototype.childView, Child, 'uses the child view');
23 | t.end();
24 | });
25 |
--------------------------------------------------------------------------------
/test/tape/components/help/about/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/help/about/Controller
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 | import Controller from '../../../../../app/scripts/components/help/about/Controller';
9 |
10 | let sand;
11 | test('help/about/Controller: before()', t => {
12 | sand = sinon.sandbox.create();
13 | t.end();
14 | });
15 |
16 | test('help/about/Controller: init()', t => {
17 | const con = new Controller();
18 | sand.stub(con, 'show');
19 | con.init();
20 | t.equal(con.show.called, true, 'calls show method');
21 |
22 | sand.restore();
23 | t.end();
24 | });
25 |
26 | test('help/about/Controller: show()', t => {
27 | const con = new Controller();
28 | const req = sand.stub(Radio, 'request');
29 | const listen = sand.stub(con, 'listenTo');
30 |
31 | con.show();
32 | t.equal(req.calledWith('Layout', 'show', {
33 | region : 'modal',
34 | view : con.view,
35 | }), true, 'renders the view in modal region');
36 | t.equal(listen.calledWith(con.view, 'destroy', con.destroy), true,
37 | 'destroyes itself if the view is destroyed');
38 |
39 | sand.restore();
40 | t.end();
41 | });
42 |
--------------------------------------------------------------------------------
/test/tape/components/help/about/view.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/help/about/View
3 | * @file
4 | */
5 | import test from 'tape';
6 | import View from '../../../../../app/scripts/components/help/about/View';
7 |
8 | test('View: className', t => {
9 | t.equal(View.prototype.className, 'modal fade');
10 | t.end();
11 | });
12 |
13 | test('View: serializeData()', t => {
14 | const view = new View({constants: 'test'});
15 | t.deepEqual(view.serializeData(), {constants: 'test'});
16 | t.end();
17 | });
18 |
--------------------------------------------------------------------------------
/test/tape/components/help/keybindings/view.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/help/keybindings/View
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | /* eslint-disable */
8 | import View from '../../../../../app/scripts/components/help/keybindings/View';
9 | /* eslint-enable */
10 |
11 | test('help/keybindings/View: className', t => {
12 | t.equal(View.prototype.className, 'modal fade');
13 | t.end();
14 | });
15 |
--------------------------------------------------------------------------------
/test/tape/components/importExport/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/importExport/main
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 | import '../../../../app/scripts/utils/underscore';
9 |
10 | import main from '../../../../app/scripts/components/importExport/main';
11 | import Import from '../../../../app/scripts/components/importExport/Import';
12 | import Export from '../../../../app/scripts/components/importExport/Export';
13 |
14 | let sand;
15 | test('importExport/Main: before()', t => {
16 | sand = sinon.sandbox.create();
17 | main();
18 | t.end();
19 | });
20 |
21 | test('importExport/Main: responds to "import" request', t => {
22 | const init = sand.stub(Import.prototype, 'init');
23 |
24 | Radio.request('components/importExport', 'import');
25 | t.equal(init.called, true);
26 |
27 | sand.restore();
28 | t.end();
29 | });
30 |
31 | test('importExport/Main: responds to "export" request', t => {
32 | const init = sand.stub(Export.prototype, 'init');
33 |
34 | Radio.request('components/importExport', 'export');
35 | t.equal(init.called, true);
36 |
37 | sand.restore();
38 | t.end();
39 | });
40 |
41 | test('importExport/Main: adds "App:checks" initializer', t => {
42 | const req = sand.stub(Radio, 'request');
43 |
44 | main();
45 | t.equal(req.calledWithMatch('utils/Initializer', 'add', {
46 | name: 'App:last',
47 | }), true, 'msg');
48 |
49 | sand.restore();
50 | t.end();
51 | });
52 |
--------------------------------------------------------------------------------
/test/tape/components/linkDialog/item.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/linkDialog/views/Item
3 | * @file
4 | */
5 | import test from 'tape';
6 | import '../../../../app/scripts/utils/underscore';
7 |
8 | import View from '../../../../app/scripts/components/linkDialog/views/Item';
9 |
10 | test('linkDialog/views/Item: tagName()', t => {
11 | t.equal(View.prototype.tagName, 'li');
12 | t.end();
13 | });
14 |
--------------------------------------------------------------------------------
/test/tape/components/linkDialog/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/linkDialog/main
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 | import '../../../../app/scripts/utils/underscore';
9 |
10 | import initialize from '../../../../app/scripts/components/linkDialog/main';
11 |
12 | test('linkDialog/main', t => {
13 | const reply = sinon.stub();
14 | sinon.stub(Radio, 'channel').returns({reply});
15 |
16 | initialize();
17 | t.equal(reply.calledWith('show'), true, 'replies to "show" request');
18 |
19 | Radio.channel.restore();
20 | t.end();
21 | });
22 |
--------------------------------------------------------------------------------
/test/tape/components/notebooks/form/tag/view.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/notebooks/form/tag/View
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | /* eslint-disable */
8 | import _ from '../../../../../../app/scripts/utils/underscore';
9 | import View from '../../../../../../app/scripts/components/notebooks/form/tag/View';
10 | import ModalForm from '../../../../../../app/scripts/behaviors/ModalForm';
11 | /* eslint-enable */
12 |
13 | test('notebooks/form/tag/View: className', t => {
14 | t.equal(View.prototype.className, 'modal fade');
15 | t.end();
16 | });
17 |
18 | test('notebooks/form/tag/View: ui()', t => {
19 | const ui = View.prototype.ui();
20 | t.equal(typeof ui, 'object', 'returns an object');
21 | t.equal(ui.name, 'input[name="name"]');
22 | t.end();
23 | });
24 |
25 | test('notebooks/form/tag/View: behaviors()', t => {
26 | const behaviors = View.prototype.behaviors();
27 | t.equal(Array.isArray(behaviors), true, 'returns an array');
28 | t.equal(behaviors.indexOf(ModalForm) !== -1, true, 'uses ModalForm behavior');
29 |
30 | t.end();
31 | });
32 |
--------------------------------------------------------------------------------
/test/tape/components/notebooks/list/notebook.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/notebooks/list/views/Notebook
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | /* eslint-disable */
8 | import _ from '../../../../../app/scripts/utils/underscore';
9 | import View from '../../../../../app/scripts/components/notebooks/list/views/Notebook';
10 | import ItemView from '../../../../../app/scripts/components/notebooks/list/views/ItemView';
11 | /* eslint-enable */
12 |
13 | test('notebooks/list/views/Tag: extends from ItemView', t => {
14 | t.equal(View.prototype instanceof ItemView, true);
15 | t.end();
16 | });
17 |
18 | test('Notebook: modelEvents()', t => {
19 | const modelEvents = View.prototype.modelEvents();
20 | t.equal(typeof modelEvents, 'object', 'returns an object');
21 | t.equal(modelEvents.change, 'render', 're-renders itself if the model has changed');
22 | t.end();
23 | });
24 |
25 | test('notebooks/list/views/Notebook: templateContext()', t => {
26 | const context = View.prototype.templateContext();
27 |
28 | context.level = 1;
29 | t.equal(context.getPadding(), '', 'returns empty string if level is equal to 1');
30 |
31 | context.level = 2;
32 | t.equal(context.getPadding(), 'padding-left:40px',
33 | 'padding-left should be equal to level*20 px');
34 |
35 | t.end();
36 | });
37 |
--------------------------------------------------------------------------------
/test/tape/components/notebooks/list/notebooks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/notebooks/list/views/Notebooks
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | /* eslint-disable */
8 | import _ from '../../../../../app/scripts/utils/underscore';
9 | import View from '../../../../../app/scripts/components/notebooks/list/views/Notebooks';
10 | import Notebook from '../../../../../app/scripts/components/notebooks/list/views/Notebook';
11 | import Navigate from '../../../../../app/scripts/behaviors/Navigate';
12 | /* eslint-enable */
13 |
14 | test('notebooks/list/views/Notebooks: className', t => {
15 | t.equal(View.prototype.className, 'list--notebooks');
16 | t.end();
17 | });
18 |
19 | test('notebooks/list/views/Notebooks: behaviors()', t => {
20 | const behaviors = View.prototype.behaviors();
21 | t.equal(Array.isArray(behaviors), true, 'returns an array');
22 | t.equal(behaviors.indexOf(Navigate) !== -1, true, 'uses navigate behavior');
23 | t.end();
24 | });
25 |
26 | test('notebooks/list/views/Notebooks: childView()', t => {
27 | t.equal(View.prototype.childView(), Notebook, 'uses notebook item view');
28 | t.end();
29 | });
30 |
31 | test('notebooks/list/views/Notebooks: childViewOptions()', t => {
32 | const view = new View({profileId: 'test', collection: 'test'});
33 | t.deepEqual(view.childViewOptions(), {profileId: 'test', filterArgs: {}}, 'msg');
34 | t.end();
35 | });
36 |
37 | test('notebooks/list/views/Notebooks: initialize()', t => {
38 | const view = new View({collection: 'test'});
39 | t.equal(_.isEmpty(view.options.filterArgs), true,
40 | 'creates options.filterArgs property');
41 | t.end();
42 | });
43 |
--------------------------------------------------------------------------------
/test/tape/components/notebooks/list/tag.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/notebooks/list/views/Tag
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | /* eslint-disable */
8 | import _ from '../../../../../app/scripts/utils/underscore';
9 | import View from '../../../../../app/scripts/components/notebooks/list/views/Tag';
10 | import ItemView from '../../../../../app/scripts/components/notebooks/list/views/ItemView';
11 | /* eslint-enable */
12 |
13 | test('notebooks/list/views/Tag: extends from ItemView', t => {
14 | t.equal(View.prototype instanceof ItemView, true);
15 | t.end();
16 | });
17 |
--------------------------------------------------------------------------------
/test/tape/components/notebooks/router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/notebooks/Router.js
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | import Router from '../../../../app/scripts/components/notebooks/Router.js';
8 | import controller from '../../../../app/scripts/components/notebooks/controller.js';
9 |
10 | test('notebooks/Router: controller', t => {
11 | t.equal(typeof Router.prototype.controller, 'object',
12 | 'is an object');
13 | t.equal(Router.prototype.controller, controller, 'uses the correct controller');
14 | t.end();
15 | });
16 |
17 | test('notebooks/Router: appRoutes', t => {
18 | const routes = Router.prototype.appRoutes;
19 | t.equal(typeof routes, 'object', 'is an object');
20 |
21 | t.equal(routes['notebooks'], 'showList',
22 | 'shows a list of notebooks and tags');
23 | t.equal(routes['notebooks/add'], 'notebookForm',
24 | 'shows notebook add form');
25 | t.equal(routes['notebooks/edit/:id'], 'notebookForm',
26 | 'shows notebook edit form');
27 | t.equal(routes['tags/add'], 'tagForm',
28 | 'shows tag add form');
29 | t.equal(routes['tags/edit/:id'], 'tagForm',
30 | 'shows tag edit form');
31 |
32 | t.end();
33 | });
34 |
--------------------------------------------------------------------------------
/test/tape/components/notes/form/views/notebook.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test: components/notes/form/views/Notebook
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 |
8 | import '../../../../../../app/scripts/utils/underscore';
9 |
10 | /* eslint-disable */
11 | import View from '../../../../../../app/scripts/components/notes/form/views/Notebook';
12 | import Notebook from '../../../../../../app/scripts/models/Notebook';
13 | /* eslint-enable */
14 |
15 | test('notes/form/Notebook: tagName', t => {
16 | t.equal(View.prototype.tagName, 'option');
17 | t.end();
18 | });
19 |
20 | test('notes/form/Notebook: onRender()', t => {
21 | const view = new View({model: new Notebook({id: '1'})});
22 | view.$el = {attr: sinon.stub()};
23 |
24 | view.onRender();
25 | t.equal(view.$el.attr.calledWith('value', '1'), true,
26 | 'changes the "value" attribute');
27 |
28 | t.end();
29 | });
30 |
--------------------------------------------------------------------------------
/test/tape/components/notes/form/views/notebooksCollection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test: components/notes/form/views/NotebooksCollection.js
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 |
8 | import _ from '../../../../../../app/scripts/utils/underscore';
9 |
10 | /* eslint-disable */
11 | import View from '../../../../../../app/scripts/components/notes/form/views/NotebooksCollection';
12 | import NotebookView from '../../../../../../app/scripts/components/notes/form/views/Notebook';
13 | /* eslint-enable */
14 |
15 | test('notes/form/views/NotebooksCollection: tagName', t => {
16 | t.equal(View.prototype.tagName, 'optgroup');
17 | t.end();
18 | });
19 |
20 | test('notes/form/views/NotebooksCollection: tagName', t => {
21 | t.equal(View.prototype.className, 'editor--notebooks--select');
22 | t.end();
23 | });
24 |
25 | test('notes/form/views/NotebooksCollection: childView()', t => {
26 | const childView = View.prototype.childView();
27 | t.equal(childView, NotebookView, 'uses Notebook view as the child view');
28 |
29 | t.end();
30 | });
31 |
32 | test('notes/form/views/NotebooksCollection: onRender()', t => {
33 | const view = View.prototype;
34 | view.$el = {attr: sinon.stub()};
35 | sinon.stub(_, 'i18n').callsFake(str => str);
36 |
37 | view.onRender();
38 | t.equal(view.$el.attr.calledWith('label', 'Notebooks'), true,
39 | 'changes view element\'s label');
40 |
41 | _.i18n.restore();
42 | t.end();
43 | });
44 |
--------------------------------------------------------------------------------
/test/tape/components/notes/router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test the core app: components/notes/Router.js
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | import Router from '../../../../app/scripts/components/notes/Router.js';
8 | import controller from '../../../../app/scripts/components/notes/controller.js';
9 |
10 | test('notes/Router: controller()', t => {
11 | t.equal(typeof Router.prototype.controller, 'object',
12 | 'is an object');
13 | t.equal(Router.prototype.controller, controller, 'uses the correct controller');
14 | t.end();
15 | });
16 |
17 | test('notes/Router: appRoutes', t => {
18 | const routes = Router.prototype.appRoutes;
19 | t.equal(typeof routes, 'object', 'is an object');
20 |
21 | t.equal(routes[''], 'showNotes', 'the index page shows a list of notes');
22 | t.equal(routes['notes(/f/:filter)(/q/:query)(/p:page)'], 'showNotes',
23 | 'properly computes routes for showNotes()');
24 |
25 | t.equal(routes['notes(/f/:filter)(/q/:query)(/p:page)/show/:id'], 'showNote',
26 | 'properly computes routes for showNote()');
27 |
28 | t.equal(routes['notes/add'], 'showForm', 'shows the add form');
29 | t.equal(routes['notes/edit/:id'], 'showForm', 'shows the edit form');
30 | t.end();
31 | });
32 |
--------------------------------------------------------------------------------
/test/tape/components/settings/router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test: components/settings/Router.js
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | import Router from '../../../../app/scripts/components/settings/Router.js';
8 | import controller from '../../../../app/scripts/components/settings/controller.js';
9 |
10 | test('settings/Router: controller()', t => {
11 | t.equal(typeof Router.prototype.controller, 'object', 'is an object');
12 | t.equal(Router.prototype.controller, controller, 'uses the correct controller');
13 | t.end();
14 | });
15 |
16 | test('settings/Router: appRoutes', t => {
17 | const routes = Router.prototype.appRoutes;
18 | t.equal(typeof routes, 'object', 'is an object');
19 | t.equal(routes['settings(/:tab)'], 'showContent');
20 | t.end();
21 | });
22 |
--------------------------------------------------------------------------------
/test/tape/components/setup/export.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/setup/export/View
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 |
8 | import '../../../../app/scripts/utils/underscore';
9 | import View from '../../../../app/scripts/components/setup/export/View';
10 |
11 | let sand;
12 | test('setup/export/View: before()', t => {
13 | sand = sinon.sandbox.create();
14 | t.end();
15 | });
16 |
17 | test('setup/export/View: serializeData()', t => {
18 | const opt = {el: 'test'};
19 | View.prototype.options = {el: 'test'};
20 |
21 | t.deepEqual(View.prototype.serializeData(), opt);
22 |
23 | View.prototype.options = null;
24 | t.end();
25 | });
26 |
--------------------------------------------------------------------------------
/test/tape/components/setup/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/setup/main
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 |
9 | import initialize from '../../../../app/scripts/components/setup/main';
10 | import Controller from '../../../../app/scripts/components/setup/Controller';
11 |
12 | let sand;
13 | test('setup/main: before()', t => {
14 | sand = sinon.sandbox.create();
15 | t.end();
16 | });
17 |
18 | test('setup/main: initialize()', t => {
19 | const init = sand.stub(Controller.prototype, 'init');
20 | const reply = sand.stub(Radio, 'reply');
21 | const req = sand.stub(Radio, 'request').callsFake((...args) => {
22 | args[2].callback();
23 | });
24 |
25 | initialize();
26 | t.equal(reply.calledWith('components/setup', 'start'), true,
27 | 'replies to "start" request');
28 |
29 | t.equal(req.calledWithMatch('utils/Initializer', 'add', {
30 | name: 'App:components',
31 | }), true, 'adds App:components initializer');
32 |
33 | t.equal(init.called, true, 'calls Controller.init method');
34 |
35 | sand.restore();
36 | t.end();
37 | });
38 |
--------------------------------------------------------------------------------
/test/tape/components/share/info.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test components/share/info/View
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 | import Mousetrap from 'mousetrap';
9 |
10 | import _ from '../../../../app/scripts/utils/underscore';
11 | import View from '../../../../app/scripts/components/share/info/View';
12 |
13 | let sand;
14 | test('share/info/View: before()', t => {
15 | sand = sinon.sandbox.create();
16 | t.end();
17 | });
18 |
19 | test('share/info/View: triggers()', t => {
20 | const triggers = View.prototype.triggers();
21 | t.equal(typeof triggers, 'object');
22 | t.equal(triggers['click .share--trust'], 'add:trust',
23 | 'triggers "add:trust" event');
24 |
25 | t.end();
26 | });
27 |
28 | test('share/info/Users: serializeData()', t => {
29 | const view = new View({test: '1'});
30 | t.equal(view.serializeData(), view.options);
31 | t.end();
32 | });
33 |
--------------------------------------------------------------------------------
/test/tape/index.js:
--------------------------------------------------------------------------------
1 | import {JSDOM} from 'jsdom';
2 | import {mkdirSync} from 'fs';
3 | import glob from 'glob';
4 | import {LocalStorage} from 'node-localstorage';
5 | import overrideTemplate from './overrideTemplate';
6 | import raf from 'raf';
7 |
8 | try {
9 | mkdirSync(`${__dirname}/../../_dev`);
10 | mkdirSync(`${__dirname}/../../_dev/scratch`);
11 | }
12 | // eslint-disable-next-line
13 | catch (e) {
14 | }
15 |
16 | global.localStorage = new LocalStorage(`${__dirname}/../../_dev/scratch`);
17 | global.overrideTemplate = overrideTemplate;
18 | global.requestAnimationFrame = raf;
19 |
20 | /**
21 | * Create DOM environment.
22 | */
23 | JSDOM.fromFile(`${__dirname}/../../app/index.html`, {
24 | url : 'http://localhost/#',
25 | contentType : 'text/html',
26 | })
27 | .then(doc => {
28 | global.document = doc.window.document;
29 | global.window = doc.window;
30 | global.navigator = global.window.navigator;
31 | global.location = global.window.location;
32 | global.HTMLElement = doc.window.HTMLElement;
33 |
34 | Object.defineProperty(global.window, 'localStorage', {
35 | get: function() {
36 | return global.localStorage;
37 | }
38 | });
39 | //global.window.localStorage = global.localStorage;
40 | global.window.setTimeout = setTimeout;
41 | global.window.clearTimeout = clearTimeout;
42 |
43 | // Automatically require all test files
44 | glob.sync(`${__dirname}/**/*.js`)
45 | .filter(file => file.indexOf('index.js') === -1)
46 | .forEach(file => require(file));
47 | });
48 |
--------------------------------------------------------------------------------
/test/tape/models/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test models/Config.js
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | import Config from '../../../app/scripts/models/Config';
8 |
9 | test('Config: idAttribute()', t => {
10 | const config = new Config();
11 | t.equal(config.idAttribute, 'name', 'uses "name" as ID attribute');
12 | t.end();
13 | });
14 |
15 | test('Config: defaults()', t => {
16 | const config = new Config();
17 |
18 | t.equal(typeof config.defaults, 'object', 'has "defaults" property');
19 | t.equal(config.defaults.name, '', 'for default name is equal to empty string');
20 | t.equal(config.defaults.value, '', 'for default value is equal to empty string');
21 |
22 | t.end();
23 | });
24 |
25 | test('Config: storeName()', t => {
26 | const config = new Config();
27 | t.equal(config.storeName, 'configs');
28 | t.end();
29 | });
30 |
31 | test('Config: validate()', t => {
32 | const config = new Config();
33 |
34 | t.equal(config.validate({name: ''}).length, 1,
35 | 'returns array of errors if name is empty');
36 |
37 | t.equal(config.validate({name: 'test'}), undefined,
38 | 'returns nothing if there are no validation errors');
39 |
40 | t.end();
41 | });
42 |
--------------------------------------------------------------------------------
/test/tape/models/diffsync/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test models/diffsync/main
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 |
9 | import initializer from '../../../../app/scripts/models/diffsync/main';
10 | import Peer from '../../../../app/scripts/models/Peer';
11 | import Core from '../../../../app/scripts/models/diffsync/Core';
12 |
13 | let sand;
14 | test('models/diffsync/main: before()', t => {
15 | sand = sinon.sandbox.create();
16 | Radio.channel('App').stopListening();
17 | t.end();
18 | });
19 |
20 | test('models/diffsync/main: initializer()', t => {
21 | const req = sand.stub(Radio, 'request')
22 | .withArgs('collections/Configs', 'findConfig', {name: 'cloudStorage'})
23 | .returns('p2p')
24 | .withArgs('collections/Configs', 'findConfigs')
25 | .returns({});
26 |
27 | const init = sand.stub(Peer.prototype, 'init');
28 | const init2 = sand.stub(Core.prototype, 'init');
29 |
30 | const start = initializer();
31 | t.equal(typeof start, 'function', 'returns the callback');
32 |
33 | start();
34 | t.equal(init.called, true, 'initializes the peer class');
35 | t.equal(init2.called, true, 'initializes the differential synchronization core');
36 |
37 | req.withArgs('collections/Configs', 'findConfig', {name: 'cloudStorage'})
38 | .returns('dropbox');
39 |
40 | start();
41 | t.equal(init.callCount, 1, 'does nothing if p2p is not used');
42 |
43 | sand.restore();
44 | t.end();
45 | });
46 |
--------------------------------------------------------------------------------
/test/tape/models/file.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test models/Config.js
3 | * @file
4 | */
5 | import test from 'tape';
6 |
7 | import File from '../../../app/scripts/models/File';
8 |
9 | test('File: storeName', t => {
10 | const file = new File();
11 | t.equal(file.storeName, 'files', 'storeName is equal to "files"');
12 | t.end();
13 | });
14 |
15 | test('File: defaults', t => {
16 | const defaults = new File().defaults;
17 |
18 | t.equal(defaults.type, 'files');
19 | t.equal(defaults.id, undefined);
20 | t.equal(defaults.name, '');
21 | t.equal(defaults.fileType, '');
22 | t.equal(defaults.trash, 0);
23 | t.equal(defaults.created, 0);
24 | t.equal(defaults.updated, 0);
25 |
26 | t.end();
27 | });
28 |
29 | test('File: validateAttributes', t => {
30 | const validate = new File().validateAttributes;
31 |
32 | t.equal(Array.isArray(validate), true, 'is an array');
33 | t.equal(validate.indexOf('src') > -1, true,
34 | 'validates "src" attribute');
35 | t.equal(validate.indexOf('fileType') > -1, true,
36 | 'validates "fileType" attribute');
37 |
38 | t.end();
39 | });
40 |
41 | test('File: escapeAttributes', t => {
42 | const escape = new File().escapeAttributes;
43 |
44 | t.equal(Array.isArray(escape), true, 'is an array');
45 | t.equal(escape.indexOf('name') > -1, true,
46 | 'filters "name" attribute');
47 |
48 | t.end();
49 | });
50 |
--------------------------------------------------------------------------------
/test/tape/models/profile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test models/Profile
3 | * @file
4 | */
5 | import test from 'tape';
6 | import Profile from '../../../app/scripts/models/Profile';
7 |
8 | test('models/Profile: storeName', t => {
9 | t.equal(new Profile().storeName, 'profiles');
10 | t.end();
11 | });
12 |
13 | test('models/Profile: idAttribute', t => {
14 | t.equal(new Profile().idAttribute, 'username');
15 | t.end();
16 | });
17 |
18 | test('models/Profile: defaults', t => {
19 | const def = new Profile().defaults;
20 | t.equal(def.username, '');
21 | t.equal(def.privateKey, '');
22 | t.equal(def.publicKey, '');
23 |
24 | t.end();
25 | });
26 |
27 | test('models/Profile: validateAttributes', t => {
28 | t.deepEqual(new Profile().validateAttributes, ['username']);
29 | t.end();
30 | });
31 |
32 | test('models/Profile: constructor()', t => {
33 | const prof = new Profile();
34 | t.equal(prof.profileId, 'default', 'profileId is always equal to "default"');
35 | t.end();
36 | });
37 |
--------------------------------------------------------------------------------
/test/tape/models/user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test models/User
3 | * @file
4 | */
5 | import test from 'tape';
6 | import User from '../../../app/scripts/models/User';
7 |
8 | test('models/User: storeName', t => {
9 | const tag = new User();
10 | t.equal(tag.storeName, 'users');
11 | t.end();
12 | });
13 |
14 | test('models/User: idAttribute', t => {
15 | t.equal(User.prototype.idAttribute, 'username');
16 | t.end();
17 | });
18 |
19 | test('models/User: defaults', t => {
20 | const defaults = new User().defaults;
21 |
22 | t.equal(defaults.type, 'users');
23 | t.equal(defaults.username, '');
24 | t.equal(defaults.fingerprint, '');
25 | t.equal(defaults.publicKey, '');
26 | t.equal(defaults.pendingAccept, false);
27 | t.equal(defaults.pendingInvite, false);
28 |
29 | t.end();
30 | });
31 |
32 | test('models/User: validateAttributes()', t => {
33 | const validate = new User().validateAttributes;
34 | t.equal(Array.isArray(validate), true, 'is an array');
35 | t.equal(validate.indexOf('username') > -1, true, 'validates username');
36 | t.equal(validate.indexOf('publicKey') > -1, true, 'validates publicKey');
37 | t.equal(validate.indexOf('fingerprint') > -1, true, 'validates fingerprint');
38 | t.end();
39 | });
40 |
41 | test('models/User: escapeAttributes()', t => {
42 | const escape = new User().escapeAttributes;
43 | t.equal(Array.isArray(escape), true, 'is an array');
44 | t.equal(escape.indexOf('username') > -1, true, 'validates username');
45 | t.equal(escape.indexOf('publicKey') > -1, true, 'validates publicKey');
46 | t.equal(escape.indexOf('fingerprint') > -1, true, 'validates fingerprint');
47 | t.end();
48 | });
49 |
--------------------------------------------------------------------------------
/test/tape/overrideTemplate.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import {readFileSync as read} from 'fs';
3 |
4 | /**
5 | * Override the template property of a View.
6 | * This file exists because both Babel and commonjs cannot handle underscore
7 | * template files.
8 | *
9 | * @param {Object} View
10 | * @param {String} path - the relative path to a template
11 | */
12 | export default function overrideTemplate(View, path) {
13 | const tmpl = read(`${__dirname}/../../app/scripts/${path}`);
14 | Object.defineProperty(View.prototype, 'template', {
15 | get: () => _.template(tmpl),
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/test/tape/utils/electronListener.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test utils/electronListener
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 |
8 | import listener from '../../../app/scripts/utils/electronListener';
9 |
10 | let sand;
11 | test('utils/electronListener: before()', t => {
12 | sand = sinon.sandbox.create();
13 | t.end();
14 | });
15 |
16 | test('utils/electronlistener: does nothing if it is not Electron environment', t => {
17 | t.equal(listener(), false);
18 | t.end();
19 | });
20 |
21 | test('utils/electronlistener: starts listening to ipcRenderer events', t => {
22 | const on = sand.stub();
23 | window.electron = {ipcRenderer: {on}};
24 |
25 | t.equal(listener(), true, 'returns true');
26 |
27 | t.equal(on.calledWith('lav:settings'), true, 'listens to lav:settings');
28 | t.equal(on.calledWith('lav:newNote'), true, 'listens to lav:newNote');
29 | t.equal(on.calledWith('lav:about'), true, 'listens to lav:about');
30 |
31 | t.equal(on.calledWith('lav:import:evernote'), true, 'listens to lav:import:evernote');
32 | t.equal(on.calledWith('lav:backup:key'), true, 'listens to lav:backup:key');
33 | t.equal(on.calledWith('lav:backup:data'), true, 'listens to lav:backup:data');
34 |
35 | window.electron = null;
36 | sand.restore();
37 | t.end();
38 | });
39 |
--------------------------------------------------------------------------------
/test/tape/utils/theme.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test: utils/theme.js
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 | import Radio from 'backbone.radio';
8 | import theme from '../../../app/scripts/utils/theme';
9 |
10 | let sand;
11 | test('utils/theme: before()', t => {
12 | sand = sinon.sandbox.create();
13 | t.end();
14 | });
15 |
16 | test('utils/theme: applyTheme()', t => {
17 | const attr = sand.stub();
18 | global.$ = sand.stub().returns({attr});
19 |
20 | sand.stub(Radio, 'request')
21 | .withArgs('collections/Configs', 'findConfig', {name: 'theme'})
22 | .returns('default');
23 |
24 | theme.applyTheme();
25 | t.equal(attr.calledWithMatch('href', 'styles/theme-default.css'), true,
26 | 'applies the default theme');
27 |
28 | theme.applyTheme({name: 'dark'});
29 | t.equal(attr.calledWith('href', 'styles/theme-dark.css'), true,
30 | 'applies the dark theme');
31 |
32 | sand.restore();
33 | t.end();
34 | });
35 |
36 | test('utils/theme: initializer()', t => {
37 | const on = sand.stub(Radio, 'on');
38 | sand.stub(theme, 'applyTheme');
39 |
40 | theme.initializer();
41 |
42 | t.equal(theme.applyTheme.called, true, 'applies the theme');
43 | t.equal(on.calledWith('components/settings', 'changeTheme', theme.applyTheme),
44 | true, 'listens to "changeTheme" event');
45 |
46 | sand.restore();
47 | t.end();
48 | });
49 |
--------------------------------------------------------------------------------
/test/tape/views/brand.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test views/Brand
3 | * @file
4 | */
5 | import test from 'tape';
6 | import sinon from 'sinon';
7 |
8 | import Brand from '../../../app/scripts/views/Brand';
9 | const brand = Brand.prototype;
10 |
11 | let sand;
12 | test('views/Brand: before()', t => {
13 | sand = sinon.sandbox.create();
14 | t.end();
15 | });
16 |
17 | test('views/Brand: onShow()', t => {
18 | brand.$el = {slideDown: sand.stub()};
19 |
20 | brand.onShow();
21 | t.equal(brand.$el.slideDown.calledWith('fast'), true,
22 | 'shows the region with animation');
23 |
24 | brand.$el = null;
25 | t.end();
26 | });
27 |
28 | test('views/Brand: onEmpty()', t => {
29 | brand.$el = {slideUp: sand.stub()};
30 |
31 | brand.onEmpty();
32 | t.equal(brand.$el.slideUp.calledWith('fast'), true,
33 | 'hides the region with animation');
34 |
35 | brand.$el = null;
36 | t.end();
37 | });
38 |
--------------------------------------------------------------------------------
/webpack.production.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Webpack configuration file for production.
5 | *
6 | * @file
7 | */
8 | const webpack = require('webpack'),
9 | config = require('./webpack.config');
10 |
11 | // Disable source maps
12 | config.devtool = false;
13 |
14 | config.plugins = config.plugins.concat([
15 |
16 | // Optimize chunk IDs
17 | new webpack.optimize.OccurrenceOrderPlugin(),
18 |
19 | // Minimize code
20 | new webpack.optimize.UglifyJsPlugin(),
21 |
22 | // Deduplicate libraries
23 | new webpack.optimize.DedupePlugin(),
24 |
25 | ]);
26 |
27 | module.exports = config;
28 |
--------------------------------------------------------------------------------