Browse Source

switch to Vue.js

pull/313/head
korelstar 2 years ago
committed by GitHub
parent
commit
fc7631eb9e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      .babelrc.js
  2. 9
      .editorconfig
  3. 79
      .eslintrc.js
  4. 3
      .gitignore
  5. 26
      .scrutinizer.yml
  6. 26
      .stylelintrc.js
  7. 29
      .travis.yml
  8. 2
      AUTHORS
  9. 29
      CONTRIBUTING.md
  10. 160
      Makefile
  11. 30
      README.md
  12. 2
      appinfo/info.xml
  13. 37
      appinfo/routes.php
  14. 5
      composer.json
  15. 32
      css/app-navigation.scss
  16. 373
      css/notes.scss
  17. BIN
      img/loading.gif
  18. 1
      img/search.svg
  19. 6
      js/.bowerrc
  20. 4
      js/.gitignore
  21. 48
      js/.jshintrc
  22. 42
      js/README.md
  23. 45
      js/app/controllers/appcontroller.js
  24. 185
      js/app/controllers/notecontroller.js
  25. 102
      js/app/controllers/notescontroller.js
  26. 26
      js/app/controllers/notessettingscontroller.js
  27. 16
      js/app/directives/autofocus.js
  28. 56
      js/app/directives/editor.js
  29. 25
      js/app/directives/tooltip.js
  30. 14
      js/app/filters/and.js
  31. 10
      js/app/filters/categoryTitle.js
  32. 22
      js/app/filters/groupNotes.js
  33. 17
      js/app/filters/wordCount.js
  34. 25
      js/app/services/debounce.js
  35. 14
      js/app/services/is.js
  36. 115
      js/app/services/notesmodel.js
  37. 80
      js/app/services/savequeue.js
  38. 40
      js/app/services/urlFinder.js
  39. 10
      js/bower.json
  40. 88
      js/config/app.js
  41. 118
      js/gulpfile.js
  42. 88
      js/karma.conf.js
  43. 32
      js/package.json
  44. 2
      js/public/app.min.js
  45. 1
      js/public/app.min.js.map
  46. 13
      js/tests/stubs/app.js
  47. 12
      js/tests/stubs/owncloud.js
  48. 63
      js/tests/unit/controllers/appcontrollerSpec.js
  49. 72
      js/tests/unit/controllers/notecontrollerSpec.js
  50. 120
      js/tests/unit/controllers/notescontrollerSpec.js
  51. 76
      js/tests/unit/directives/timeoutchangeSpec.js
  52. 36
      js/tests/unit/filters/andfilterSpec.js
  53. 19
      js/tests/unit/services/isSpec.js
  54. 65
      js/tests/unit/services/notesmodelSpec.js
  55. 116
      js/tests/unit/services/savequeueSpec.js
  56. 20
      js/vendor/angular-route/.bower.json
  57. 21
      js/vendor/angular-route/LICENSE.md
  58. 68
      js/vendor/angular-route/README.md
  59. 1071
      js/vendor/angular-route/angular-route.js
  60. 16
      js/vendor/angular-route/angular-route.min.js
  61. 8
      js/vendor/angular-route/angular-route.min.js.map
  62. 10
      js/vendor/angular-route/bower.json
  63. 2
      js/vendor/angular-route/index.js
  64. 33
      js/vendor/angular-route/package.json
  65. 18
      js/vendor/angular/.bower.json
  66. 21
      js/vendor/angular/LICENSE.md
  67. 64
      js/vendor/angular/README.md
  68. 21
      js/vendor/angular/angular-csp.css
  69. 32621
      js/vendor/angular/angular.js
  70. 324
      js/vendor/angular/angular.min.js
  71. BIN
      js/vendor/angular/angular.min.js.gzip
  72. 8
      js/vendor/angular/angular.min.js.map
  73. 9
      js/vendor/angular/bower.json
  74. 2
      js/vendor/angular/index.js
  75. 25
      js/vendor/angular/package.json
  76. 29
      js/vendor/restangular/.bower.json
  77. 24
      js/vendor/restangular/.github/ISSUE_TEMPLATE.md
  78. 19
      js/vendor/restangular/.gitignore
  79. 23
      js/vendor/restangular/.jshintrc
  80. 11
      js/vendor/restangular/.travis.yml
  81. 77
      js/vendor/restangular/CHANGELOG.md
  82. 67
      js/vendor/restangular/CONTRIBUTING.md
  83. 188
      js/vendor/restangular/Gruntfile.js
  84. 1386
      js/vendor/restangular/README.md
  85. 18
      js/vendor/restangular/bower.json
  86. 1452
      js/vendor/restangular/dist/restangular.js
  87. 6
      js/vendor/restangular/dist/restangular.min.js
  88. BIN
      js/vendor/restangular/dist/restangular.zip
  89. 89
      js/vendor/restangular/karma.conf.js
  90. 77
      js/vendor/restangular/karma.underscore.conf.js
  91. 21
      js/vendor/restangular/license.md
  92. 63
      js/vendor/restangular/package.json
  93. 1447
      js/vendor/restangular/src/restangular.js
  94. 35
      js/vendor/simplemde/.bower.json
  95. 10
      js/vendor/simplemde/CONTRIBUTING.md
  96. 22
      js/vendor/simplemde/LICENSE
  97. 331
      js/vendor/simplemde/README.md
  98. 23
      js/vendor/simplemde/bower.json
  99. 676
      js/vendor/simplemde/debug/simplemde.css
  100. 17023
      js/vendor/simplemde/debug/simplemde.debug.js

13
.babelrc.js

@ -0,0 +1,13 @@
module.exports = {
plugins: ['@babel/plugin-syntax-dynamic-import'],
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['last 2 versions', 'ie >= 11']
}
}
]
]
}

js/vendor/restangular/.editorconfig → .editorconfig

79
.eslintrc.js

@ -0,0 +1,79 @@
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
ecmaVersion: 6,
},
settings: {
'import/resolver': {
webpack: {
config: 'webpack.common.js',
},
node: {
paths: ['src'],
extensions: ['.js', '.vue'],
},
},
},
extends: [
'eslint:recommended',
'plugin:import/recommended',
'plugin:vue/recommended',
'standard',
],
globals: {
$: 'readonly',
t: 'readonly',
n: 'readonly',
OC: 'readonly',
OCA: 'readonly',
},
plugins: [
'vue',
],
rules: {
// allow space before function () (was "always" in "standard")
'space-before-function-paren': ['error', 'never'],
// stay consistent with array brackets (not in "standard")
'array-bracket-newline': ['error', 'consistent'],
// tabs only (was spaces in "standard")
'indent': ['error', 'tab'],
// allow tabs for indentation (was forbidden in "standard")
'no-tabs': ['error', { allowIndentationTabs: true }],
// indentation in vue's html should be tabs (was spaces in "vue/strongly-recommended")
'vue/html-indent': ['error', 'tab'],
// only debug console (not in "standard")
'no-console': ['error', { allow: ['error', 'warn', 'info', 'debug'] }],
// always add a trailing comma, for diff readability (was "never" in "standard")
'comma-dangle': ['warn', 'always-multiline'],
// always have the operator in front (was "after" in "standard")
'operator-linebreak': ['error', 'before'],
// ternary on multiline (not in "standard")
'multiline-ternary': ['error', 'always-multiline'],
// disallow use of "var" (not in "standard")
'no-var': 'error',
// Suggest using const
'prefer-const': 'warn',
// check case of component names (not in "vue/recommended")
'vue/component-name-in-template-casing': 'error',
// no ending html tag on a new line (was warn in "vue/strongly-recommended")
'vue/html-closing-bracket-newline': 'error',
// space before self-closing elements (was warn in "vue/strongly-recommended")
'vue/html-closing-bracket-spacing': 'error',
// code spacing with attributes (default is 1)
'vue/max-attributes-per-line': [
'error',
{
singleline: 3,
multiline: {
max: 3,
allowFirstLine: true,
}
}
],
},
}

3
.gitignore

@ -1,6 +1,7 @@
node_modules/
*.log
build/artifacts
build/
js/
.rvm
report
clover.xml

26
.scrutinizer.yml

@ -1,16 +1,18 @@
filter:
excluded_paths:
- 'js/vendor/*'
- 'js/public/*'
- 'js/tests/stubs/*'
- 'templates/*'
- 'l10n/*'
- 'tests/*'
excluded_paths:
- 'js/*'
- 'templates/*'
- 'l10n/*'
- 'tests/*'
imports:
- javascript
- php
build:
nodes:
analysis:
tests:
override:
- php-scrutinizer-run
- eslint-run --ext .js,.vue src
tools:
external_code_coverage:
timeout: 1800
external_code_coverage: false
js_hint: false

26
.stylelintrc.js

@ -0,0 +1,26 @@
module.exports = {
extends: 'stylelint-config-recommended-scss',
rules: {
indentation: 'tab',
'selector-type-no-unknown': null,
'number-leading-zero': null,
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'inside-block']
}
],
'declaration-empty-line-before': [
'never',
{
ignore: ['after-declaration']
}
],
'comment-empty-line-before': null,
'selector-type-case': null,
'selector-list-comma-newline-after': null,
'no-descending-specificity': null,
'string-quotes': 'single'
},
plugins: ['stylelint-scss']
}

29
.travis.yml

@ -7,6 +7,10 @@ addons:
packages:
- libxml2-utils
cache:
directories:
- node_modules
env:
global:
- CORE_BRANCH=master
@ -44,24 +48,9 @@ script:
# - phpunit --coverage-clover clover.xml -c phpunit.xml
# - phpunit -c phpunit.integration.xml
# execute js tests
- cd js
- npm install
- npm install -g gulp
#- gulp test
- gulp
# Create coverage report
- sh -c "if [ '$TRAVIS_PHP_VERSION' != 'hhvm' ]; then wget https://scrutinizer-ci.com/ocular.phar; fi"
- sh -c "if [ '$TRAVIS_PHP_VERSION' != 'hhvm' ]; then php ocular.phar code-coverage:upload --format=php-clover clover.xml; fi"
#matrix:
# include:
# - php: 7.1
# env: DB=mysql
# - php: 7.0
# env: "DB=mysql CORE_BRANCH=stable9.1"
# - php: 5.6
# env: "DB=sqlite CORE_BRANCH=stable9"
# fast_finish: true
# build js
- make npm-init
- make lint
- make stylelint
- make build-js-production

2
AUTHORS

@ -1,2 +0,0 @@
Jan-Christoph Borchardt (http://jancborchardt.net)
Bernhard Posselt <dev@bernhard-posselt.com>

29
CONTRIBUTING.md

@ -1,29 +0,0 @@
## Submitting issues
If you have questions about how to install or use Nextcloud, please direct these to the [mailing list][mailinglist] or our [forum][forum]. We are also available on [IRC][irc].
### Short version
* The [**issue template can be found here**][template]. Please always use the issue template when reporting issues.
### Guidelines
* Please search the existing issues first, it's likely that your issue was already reported or even fixed.
- Go to one of the repositories, click "issues" and type any word in the top search/command bar.
- You can also filter by appending e. g. "state:open" to the search string.
- More info on [search syntax within github](https://help.github.com/articles/searching-issues)
* This repository ([notes](https://github.com/nextcloud/notes/issues)) is *only* for issues within the Nextcloud notes code.
* __SECURITY__: Report any potential security bug to security@nextcloud.com following our [security policy](https://nextcloud.com/security/) instead of filing an issue in our bug tracker
* Report the issue using our [template][template], it includes all the information we need to track down the issue.
Help us to maximize the effort we can spend fixing issues and adding new features, by not reporting duplicate issues.
[template]: https://raw.github.com/nextcloud/server/master/issue_template.md
[mailinglist]: https://mailman.owncloud.org/mailman/listinfo/owncloud
[forum]: https://help.nextcloud.com/
[irc]: https://webchat.freenode.net/?channels=nextcloud&uio=d4
[irc-dev]: https://webchat.freenode.net/?channels=nextcloud-dev&uio=d4
### Contribute Code and translations
Please check [core's contribution guidelines](https://github.com/nextcloud/server/blob/master/CONTRIBUTING.md) for further information about contributing code and translations.
For further development questions / discussions you can also join the [#nextcloud-dev][irc-dev] IRC channel.

160
Makefile

@ -1,23 +1,6 @@
#
# Nextcloud scaffolder tool
#
# Copyright (C) 2013 Bernhard Posselt, <nukewhale@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Makefile for building the project
### build and sign release
app_name=notes
project_dir=$(CURDIR)/../$(app_name)
build_dir=$(CURDIR)/build/artifacts
@ -26,80 +9,6 @@ appstore_dir=$(build_dir)/appstore
package_name=$(app_name)
cert_dir=$(HOME)/.nextcloud/certificates
# binary directories for running the CI tests
firefox_bin=/usr/bin/firefox
chrome_bin=/usr/bin/chromium
phantomjs_bin=/usr/bin/phantomjs
js_dir=$(CURDIR)/js
js_public_dir=$(js_dir)/public
# common directories
grunt_dir=$(js_dir)/node_modules/grunt-cli/bin/grunt
bower_dir=$(js_dir)/node_modules/bower/bin/bower
gruntfile_dir=$(js_dir)/Gruntfile.js
php_unit_tests_dir=$(CURDIR)/tests/unit
php_integration_tests_dir=$(CURDIR)/tests/integration
php_acceptance_tests_dir=$(CURDIR)/tests/acceptance
# building the javascript
all: build
build: deps
mkdir -p $(js_public_dir)
$(grunt_dir) --config $(gruntfile_dir) build
watch: build
$(grunt_dir) --config $(gruntfile_dir) watch:concat
update: deps
$(bower_dir) update
# testing
tests: js-unit-tests php-unit-tests php-integration-tests php-acceptance-tests
unit-tests: js-unit-tests php-unit-tests
# testing js
js-unit-tests: deps
export PHANTOMJS_BIN=$(phantomjs_bin) && \
$(grunt_dir) --config $(gruntfile_dir) karma:continuous
watch-js-unit-tests: deps
export CHROME_BIN=$(chrome_bin) && export FIREFOX_BIN=$(firefox_bin) && \
$(grunt_dir) --config $(gruntfile_dir) karma:unit
# testing php
php-unit-tests: deps
phpunit $(php_unit_tests_dir)
watch-php-unit-tests: deps
$(grunt_dir) --config $(gruntfile_dir) watch:phpunit
php-integration-tests: deps
phpunit $(php_integration_tests_dir)
php-acceptance-tests: deps
cd $(php_acceptance_tests_dir); make headless
# general
deps:
cd js
npm install --deps
cd ..
clean:
rm -rf $(CURDIR)/node_modules
rm -rf $(build_dir)
dist: appstore
appstore: clean
mkdir -p $(sign_dir)
rsync -a \
@ -128,10 +37,71 @@ appstore: clean
--exclude=js/package.json \
$(project_dir) $(sign_dir)
@echo "Signing…"
php ../../occ integrity:sign-app \
php ../server/occ integrity:sign-app \
--privateKey=$(cert_dir)/$(app_name).key\
--certificate=$(cert_dir)/$(app_name).crt\
--path=$(sign_dir)/$(app_name)
tar -czf $(build_dir)/$(app_name).tar.gz \
-C $(sign_dir) $(app_name)
openssl dgst -sha512 -sign $(cert_dir)/$(app_name).key $(build_dir)/$(app_name).tar.gz | openssl base64
### from vueexample
all: dev-setup lint build-js-production test
# Dev env management
dev-setup: clean clean-dev npm-init
npm-init:
npm install
npm-upgrade:
npm-upgrade
npm install
npm-update:
npm update
# Building
build-js:
npm run dev
build-js-production:
npm run build
watch-js:
npm run watch
# Testing
test:
npm run test
test-watch:
npm run test:watch
test-coverage:
npm run test:coverage
# Linting
lint:
npm run lint
lint-fix:
npm run lint:fix
# Style linting
stylelint:
npm run stylelint
stylelint-fix:
npm run stylelint:fix
# Cleaning
clean:
rm -f js/notes.js
rm -f js/notes.js.map
clean-dev:
rm -rf node_modules

30
README.md

@ -1,9 +1,5 @@
# Notes
<!--
[![build state](https://travis-ci.org/nextcloud/notes.png)](https://travis-ci.org/nextcloud/notes) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/nextcloud/notes/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/nextcloud/notes/?branch=master)
-->
<!-- The following paragraph should be kept synchronized with the description in appinfo/info.xml -->
The Notes app is a distraction free notes taking app for [Nextcloud](https://www.nextcloud.com/). It provides categories for better organization and supports formatting using [Markdown](https://en.wikipedia.org/wiki/Markdown) syntax. Notes are saved as files in your Nextcloud, so you can view and edit them with every Nextcloud client. Furthermore, a separate [RESTful API](https://github.com/nextcloud/notes/wiki/API-0.2) allows for an easy integration into third-party apps (currently, there are notes apps for [Android](https://github.com/stefan-niedermann/nextcloud-notes), [iOS](https://github.com/owncloud/notes-iOS-App) and the [console](https://git.danielmoch.com/nncli/about) which allow convenient access to your Nextcloud notes). Further features include marking notes as favorites.
@ -30,28 +26,16 @@ Before reporting bugs:
- [Lukas Reschke](https://github.com/LukasReschke)
## :warning: Git (development version)
**Installation**
* Clone the **Notes** app into the `/var/www/nextcloud/apps/` directory
`git clone https://github.com/nextcloud/notes.git`
* Activate the **Notes** app in the apps menu
**Keep up to date**
To update the Notes app use::
cd /var/www/nextcloud/apps/notes
git pull --rebase origin master
## :warning: Developer Info
[![build state](https://travis-ci.org/nextcloud/notes.png)](https://travis-ci.org/nextcloud/notes)
**Building JavaScript**
**Building the app**
If you want to change some JavaScript code, you have to consolidate the files in a build. Please follow the instructions in the [JavaScript directory](js/README.md).
1. Clone this into your `apps` folder of your Nextcloud
2. In a terminal, run the command `make dev-setup` to install the dependencies
3. Then to build the Javascript and whenever you make changes, run `make build-js`
4. Enable the app through the app management of your Nextcloud
**Third-party apps**

2
appinfo/info.xml

@ -22,6 +22,6 @@ The Notes app is a distraction free notes taking app. It provides categories for
<repository type="git">https://github.com/nextcloud/notes.git</repository>
<screenshot small-thumbnail="https://raw.githubusercontent.com/nextcloud/screenshots/master/apps/Notes/notes-thumbnail.jpg">https://raw.githubusercontent.com/nextcloud/screenshots/master/apps/Notes/notes.png</screenshot>
<dependencies>
<nextcloud min-version="14" max-version="17" />
<nextcloud min-version="15" max-version="17" />
</dependencies>
</info>

37
appinfo/routes.php

@ -10,26 +10,27 @@
*/
return ['routes' => [
// page
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
// page
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'page#index', 'url' => '/welcome', 'verb' => 'GET', 'postfix' => 'welcome'],
['name' => 'page#index', 'url' => '/note/{id}', 'verb' => 'GET', 'postfix' => 'note', 'requirements' => ['id' => '\d+']],
// notes
['name' => 'notes#index', 'url' => '/notes', 'verb' => 'GET'],
['name' => 'notes#get', 'url' => '/notes/{id}', 'verb' => 'GET'],
['name' => 'notes#create', 'url' => '/notes', 'verb' => 'POST'],
['name' => 'notes#update', 'url' => '/notes/{id}', 'verb' => 'PUT'],
['name' => 'notes#category', 'url' => '/notes/{id}/category', 'verb' => 'PUT'],
['name' => 'notes#favorite', 'url' => '/notes/{id}/favorite', 'verb' => 'PUT'],
['name' => 'notes#destroy', 'url' => '/notes/{id}', 'verb' => 'DELETE'],
// notes
['name' => 'notes#index', 'url' => '/notes', 'verb' => 'GET'],
['name' => 'notes#get', 'url' => '/notes/{id}', 'verb' => 'GET', 'requirements' => ['id' => '\d+']],
['name' => 'notes#create', 'url' => '/notes', 'verb' => 'POST'],
['name' => 'notes#update', 'url' => '/notes/{id}', 'verb' => 'PUT', 'requirements' => ['id' => '\d+']],
['name' => 'notes#category', 'url' => '/notes/{id}/category', 'verb' => 'PUT', 'requirements' => ['id' => '\d+']],
['name' => 'notes#favorite', 'url' => '/notes/{id}/favorite', 'verb' => 'PUT', 'requirements' => ['id' => '\d+']],
['name' => 'notes#destroy', 'url' => '/notes/{id}', 'verb' => 'DELETE', 'requirements' => ['id' => '\d+']],
// api
['name' => 'notes_api#index', 'url' => '/api/v0.2/notes', 'verb' => 'GET'],
['name' => 'notes_api#get', 'url' => '/api/v0.2/notes/{id}', 'verb' => 'GET'],
['name' => 'notes_api#create', 'url' => '/api/v0.2/notes', 'verb' => 'POST'],
['name' => 'notes_api#update', 'url' => '/api/v0.2/notes/{id}', 'verb' => 'PUT'],
['name' => 'notes_api#destroy', 'url' => '/api/v0.2/notes/{id}', 'verb' => 'DELETE'],
['name' => 'notes_api#preflighted_cors', 'url' => '/api/v0.2/{path}',
'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
// api
['name' => 'notes_api#index', 'url' => '/api/v0.2/notes', 'verb' => 'GET'],
['name' => 'notes_api#get', 'url' => '/api/v0.2/notes/{id}', 'verb' => 'GET', 'requirements' => ['id' => '\d+']],
['name' => 'notes_api#create', 'url' => '/api/v0.2/notes', 'verb' => 'POST'],
['name' => 'notes_api#update', 'url' => '/api/v0.2/notes/{id}', 'verb' => 'PUT', 'requirements' => ['id' => '\d+']],
['name' => 'notes_api#destroy', 'url' => '/api/v0.2/notes/{id}', 'verb' => 'DELETE', 'requirements' => ['id' => '\d+']],
['name' => 'notes_api#preflighted_cors', 'url' => '/api/v0.2/{path}', 'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
// settings
['name' => 'settings#set', 'url' => '/settings', 'verb' => 'PUT'],

5
composer.json

@ -0,0 +1,5 @@
{
"require-dev": {
"christophwurst/nextcloud": "^15.0"
}
}

32
css/app-navigation.scss

@ -0,0 +1,32 @@
#app-navigation li.collapsible.category-header:not(.open) a {
font-weight: bold;
}
#app-navigation > ul > li.app-navigation-caption {
padding: 0;
pointer-events: inherit;
}
/* icons for sidebar */
.nav-icon-files {
@include icon-color('folder', 'notes', $color-black);
}
.nav-icon-emptyfolder {
@include icon-color('folder-empty', 'notes', $color-black);
}
.nav-icon-recent {
@include icon-color('recent', 'notes', $color-black);
}
.app-navigation-entry-utils-menu-button {
visibility: hidden;
}
.active .app-navigation-entry-utils-menu-button,
li:hover .app-navigation-entry-utils-menu-button,
li:focus .app-navigation-entry-utils-menu-button {
visibility: visible;
}

373
css/notes.scss

@ -1,372 +1 @@
/**
* Copyright (c) 2013, Jan-Christoph Borchardt http://jancborchardt.net
* and others
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
#app {
width: 100%;
}
#app-content {
height: 100%;
}
#app-navigation > ul > li.has-error a{
color: var(--color-error);
}
#app-navigation li .nav-entry {
display: block;
width: 100%;
line-height: 44px;
min-height: 44px;
padding: 0 12px;
overflow: hidden;
box-sizing: border-box;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-main-text);
opacity: .57;
}
#app-navigation li .nav-entry .emptycontent-search {
white-space: normal;
}
@media (max-height: 600px) {
#app-navigation li .nav-entry .emptycontent-search {
margin-top: inherit;
}
}
#app-navigation li.search-result-header > a,
#app-navigation li.search-result-header > a * {
font-style: italic;
cursor: default;
}
#app-navigation li:hover .nav-entry,
#app-navigation li:focus .nav-entry {
opacity: 1;
}
/* only display the delete button when the note is active */
#app-navigation .app-navigation-entry-utils-menu-button {
display: none;
}
#app-navigation .app-navigation-entry-utils-menu-button.button-star.starred,
#app-navigation .active .app-navigation-entry-utils-menu-button.button-delete,
#app-navigation .active .app-navigation-entry-utils-menu-button.button-star,
#app-navigation li:hover .app-navigation-entry-utils-menu-button.button-delete,
#app-navigation li:hover .app-navigation-entry-utils-menu-button.button-star {
display: inline-block;
}
#app-navigation .app-navigation-entry-utils .app-navigation-entry-utils-menu-button .icon-delete {
opacity: .3;
}
#app-navigation .app-navigation-entry-utils-menu-button .icon-starred {
display: inline-block;
opacity: 1 !important;
}
#app-settings-content > div + div {
padding-top: 2ex;
}
#app-settings-content form,
#app-content .note-meta > .note-category > form {
display: inline-flex;
}
.tooltip {
text-shadow: none;
text-transform: none;
}
#app-content-container {
height: 100%;
}
/* special styling to make editor subtle and fit the window */
.CodeMirror {
position: relative;
min-height: 100%;
max-width: 47em;
margin: 0 0 -50px;
padding: 30px 0 90px;
border: none;
font-size: 16px;
line-height: 1.5em;
}
.CodeMirror-scroll {
overflow-x: auto !important;
}
/* enable clickthrough for links */
.CodeMirror-cursor {
pointer-events: none;
border-color: var(--color-main-text);
}
#app-content .note-meta {
position: fixed;
bottom: 0;
width: 100%;
padding: 0 45px 0px 45px;
margin: -10px 0 0;
background-color: var(--color-main-background);
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
z-index: 5;
}
#app-content .note-meta > * {
opacity: .5;
}
#app-content .note-meta > .note-category {
padding-right: 1ex;
}
#app-content .note-meta > .note-error {
opacity: 1;
background-color: var(--color-error);
color: var(--color-primary-text);
border-radius: 0.5ex;
padding: 0.5ex 1ex;
}
#app-content .note-meta-right {
position: fixed;
bottom: 10px;
right: 10px;
}
#app-content .note-meta > .saving {
display: inline-block;
vertical-align: middle;
width: 2.5ex;
height: 2.5ex;
background: url('../img/loading.gif') no-repeat;
background-size: contain;
opacity: 1;
margin: 0 1ex;
}
#app-content .note-meta form {
display: inline;
}
#app-content .note-meta .note-category {
cursor: pointer;
}
#app-content .note-meta .note-category .icon-loading-small {
display: inline-block;
vertical-align: middle;
margin: 0px 5px;
}
#app-content .note-meta .edit {
background-color: transparent;
border: none;
opacity: 0.5;
vertical-align: middle;
}
#app-content .note-meta:hover .note-category,
#app-content .note-meta:hover .edit {
opacity: 1;
}
.btn-fullscreen {
padding: 15px;
}
/* category and auto-complete */
form.category #category {
width: 15em;
}
form.category .icon-confirm {
margin-right: 0;
}
.ui-autocomplete.category-autocomplete {
z-index: 5000 !important;
position: fixed;
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
border-top: 1px solid var(--color-border);
border-radius: 0;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.category-autocomplete .ui-menu-item a {
font-weight: inherit;
white-space: nowrap;
}
/* markdown styling */
.CodeMirror {
background: transparent;
color: inherit;
}
.CodeMirror .CodeMirror-code {
width: 100%;
}
.CodeMirror .CodeMirror-code .cm-header {
/* break from core in using semibold, otherwise not emphasized in texts */
font-weight: 600;
}
.CodeMirror .CodeMirror-code .cm-header-1 {
font-size: 28px;
margin: 50px 0 20px;
line-height: 120%;
}
.CodeMirror .CodeMirror-code .cm-header-2 {
font-size: 20px;
margin-top: 12px;
line-height: 150%;
}
.CodeMirror .CodeMirror-code .cm-header-3,
.CodeMirror .CodeMirror-code .cm-header-4,
.CodeMirror .CodeMirror-code .cm-header-5,
.CodeMirror .CodeMirror-code .cm-header-6 {
font-size: 16px;
margin: 0;
font-weight: 300;
}
.CodeMirror .CodeMirror-code .cm-header-3 {
font-weight: 600;
}
.CodeMirror .CodeMirror-code .cm-hr {
position: relative;
display: inline-block;
width: 100%;
}
.CodeMirror .CodeMirror-code .cm-hr:before {
position: absolute;
content: "";
top: 50%;
width: 100%;
z-index: -1;
border-top: 5px solid rgba(120, 120, 120, 0.2);
}
/* hanging punctuation */
.CodeMirror .CodeMirror-code .CodeMirror-line {
padding-left: 45px;
}
.CodeMirror .CodeMirror-code .cm-formatting-header:not(:only-child),
.CodeMirror .CodeMirror-code .cm-formatting-list,
.CodeMirror .CodeMirror-code .cm-formatting-quote {
position: absolute;
display: inline-block;
width: 90px;
margin-top: 0;
margin-left: -90px;
text-align: right;
white-space: pre;
color: rgba(120, 120, 120, 0.5);
}
.CodeMirror .CodeMirror-code .cm-formatting-header:not(:first-child) {
width: auto;
margin-left: 0px;
}
.CodeMirror .CodeMirror-code .cm-comment {
font-family: MONOSPACE;
}
/* Checkboxes */
.CodeMirror .CodeMirror-code .cm-formatting-task {
position: relative;
display: inline-block;
width: 1.8em;
}
.CodeMirror .CodeMirror-code .cm-formatting-task.cm-meta::before {
content: "\2610";
font-size: 1.5em;
background: white;
position: absolute;
}
.CodeMirror .CodeMirror-code .cm-formatting-task.cm-property::before {
content: "\2611";
font-size: 1.5em;
background: white;
position: absolute;
}
.CodeMirror .CodeMirror-code .CodeMirror-line.completed-task {
color: #999;
text-decoration: line-through;
}
/* Recommended tab size increase for readability */
.CodeMirror .CodeMirror-code .cm-tab {
width: 2em;
}
/* distraction free styles */
:-webkit-full-screen,
:-moz-full-screen,
:-ms-fullscreen,
:fullscreen {
width: 100%;
background-color: var(--color-main-background);
#app-content-container {
margin: 0 auto;
}
}
/* larger screen sizes */
@media only screen and (min-width: 769px) {
/* use slightly more space on the left so all # signs of h3–h6 show */
.CodeMirror .CodeMirror-code .CodeMirror-line,
#app-content .note-meta {
padding-left: 90px;
}
}
/* icons for sidebar */
.nav-icon-files {
@include icon-color('folder', 'notes', $color-black);
}
.nav-icon-emptyfolder {
@include icon-color('folder-empty', 'notes', $color-black);
}
.nav-icon-recent {
@include icon-color('recent', 'notes', $color-black);
}
.nav-icon-search {
@include icon-color('search', 'notes', $color-black);
}
.separator-below {
border-bottom: 1px solid var(--color-border);
}
.separator-above {
border-top: 1px solid var(--color-border);
}
.current-category-item > a,
a.current-category-item {
font-weight: bold;
}
@import 'app-navigation.scss';

BIN
img/loading.gif

Before

Width: 32  |  Height: 32  |  Size: 3.1 KiB

1
img/search.svg

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 16 16" height="16" width="16"><g stroke="#000" stroke-width="2" fill="none"><ellipse rx="4" ry="4" cy="6" cx="6"/><path d="m14.3 14.25-5.65-5.65"/></g></svg>

6
js/.bowerrc

@ -1,6 +0,0 @@
{
"directory": "vendor",
"ignoredDependencies": [
"lodash"
]
}

4
js/.gitignore

@ -1,4 +0,0 @@
node_modules/
*.log
test-results.xml
vendor/**/test

48
js/.jshintrc

@ -1,48 +0,0 @@
{
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"forin": false,
"immed": true,
"indent": 4,
"latedef": true,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
"plusplus": true,
"quotmark": "single",
"undef": true,
"unused": true,
"strict": true,
"maxparams": false,
"maxdepth": 3,
"maxlen": 80,
"browser": true,
"devel": true,
"jquery": true,
"globals": {
"angular": true,
"app": true,
"OC": true,
"requestToken": true,
"inject": true,
"module": true,
"protractor": true,
"browser": true,
"By": true,
"it": true,
"afterEach": true,
"jasmine": true,
"describe": true,
"beforeEach": true,
"expect": true,
"exports": true,
"t": true,
"mdEdit": true,
"require": true,
"__dirname": true
}
}

42
js/README.md

@ -1,42 +0,0 @@
# Building JavaScript and CSS
To build JavaScript and CSS first install Gulp and install the required modules via npm:
sudo npm install -g gulp
npm install
To simply build everything run:
gulp
You can also run it in watch mode to rebuild when files change:
gulp watch
# Tests
Run all tests:
gulp test-all
Run JavaScript tests:
gulp test
in watch mode:
gulp watch-test
Run PHP unit tests:
gulp test-php
in watch mode:
gulp watch-test-php
Run PHP integration tests:
gulp test-php-integration

45
js/app/controllers/appcontroller.js

@ -1,45 +0,0 @@
/**
* Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING file.
*/
app.controller('AppController', function ($scope, $location, is) {
'use strict';
$scope.is = is;
$scope.init = function (lastViewedNote, errorMessage) {
$scope.defaultTitle = document.title;
if(lastViewedNote !== 0 && $location.path()==='') {
$location.path('/notes/' + lastViewedNote);
}
if(errorMessage) {
OC.Notification.showTemporary(errorMessage);
}
$scope.initSearch();
};
$scope.search = '';
$scope.defaultTitle = null;
$scope.initSearch = function() {
new OCA.Search(
function (query) {
$scope.search = query;
$scope.$apply();
if($('#app-navigation-toggle').css('display')!=='none' &&
!$('body').hasClass('snapjs-left')) {
$('#app-navigation-toggle').click();
}
},
function () {
$scope.search = '';
$scope.$apply();
}
);
};
});

185
js/app/controllers/notecontroller.js

@ -1,185 +0,0 @@
/**
* Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING file.
*/
app.controller('NoteController', function($routeParams, $scope, NotesModel,
SaveQueue, note, debounce,
$document, $timeout) {
'use strict';
NotesModel.updateIfExists(note);
$scope.note = NotesModel.get($routeParams.noteId);
$scope.isSaving = function () {
return SaveQueue.isSaving();
};
$scope.isManualSaving = function () {
return SaveQueue.isManualSaving();
};
$scope.updateTitle = function () {
var content = $scope.note.content;
// prepare content: remove markdown characters and empty spaces
content = content.replace(/^\s*[*+-]\s+/mg, ''); // list item
content = content.replace(/^#+\s+(.*?)\s*#*$/mg, '$1'); // headline
content = content.replace(/^(=+|-+)$/mg, ''); // separate headline
content = content.replace(/(\*+|_+)(.*?)\1/mg, '$2'); // emphasis
// prevent directory traversal, illegal characters
content = content.replace(/[\*\|\/\\\:\"<>\?]/g, '');
// prevent unintended file names
content = content.replace(/^[\. ]+/mg, '');
// generate title from the first line of the content
$scope.note.title = content.trim().split(/\r?\n/, 2)[0] ||
t('notes', 'New note');
};
$scope.toggleCheckbox = function (el) {
var $el = $(el);
var cm = $('.CodeMirror')[0].CodeMirror;
var doc = cm.getDoc();
var index = $el.parents('.CodeMirror-line').index();
var line = doc.getLineHandle(index);
var newvalue = ( $el.text() === '[x]' ) ? '[ ]' : '[x]';
// + 1 for some reason... not sure why
doc.replaceRange(newvalue,
{line: index, ch: line.text.indexOf('[')},
{line: index, ch: line.text.indexOf(']') + 1}
);
};
$scope.onEdit = function() {
var note = $scope.note;
note.unsaved = true;
$scope.autoSave(note);
};
$scope.autoSave = debounce(function(note) {
SaveQueue.add(note);
}, 1000);
$scope.manualSave = function() {
var note = $scope.note;
note.error = false;
SaveQueue.addManual(note);
};
$scope.editCategory = false;
$scope.showEditCategory = function() {
$('#category').val($scope.note.category);
$scope.editCategory = true;
$('#category').autocomplete({
source: NotesModel.getCategories(NotesModel.getAll(), 0, false),
minLength: 0,
position: { my: 'left bottom', at: 'left top', of: '#category' },
open: function() {
$timeout(function() {
var width = $('form.category').innerWidth() - 2;
$('.ui-autocomplete.ui-menu').width(width);
});
},
}).autocomplete('widget').addClass('category-autocomplete');
// fix space between input and confirm-button
$('form.category .icon-confirm').insertAfter('#category');
$timeout(function() {
$('#category').focus();
$('#category').autocomplete('search', '');
});
};
$scope.saveCategory = function () {
var category = $('#category').val();
if($scope.note.category === category) {
$scope.editCategory = false;
return;
}
$scope.isCategorySaving = true;
$scope.note.customPUT({category: category}, 'category', {}, {})
.then(
function (updatedNote) {
$scope.note.category = updatedNote.category;
if(category !== updatedNote.category) {
OC.Notification.showTemporary(
t('notes', 'Updating the note\'s category has failed.'+
' Is the target directory writable?')
);
}
},
function () {
OC.Notification.showTemporary(
t('notes', 'Updating the note\'s category has failed.')
);
}
)
.finally(
function () {
$scope.isCategorySaving = false;
$scope.editCategory = false;
}
);
};
$document.unbind('keypress.notes.save');
$document.bind('keypress.notes.save', function(event) {
if(event.ctrlKey || event.metaKey) {
switch(String.fromCharCode(event.which).toLowerCase()) {
case 's':
event.preventDefault();
$scope.manualSave();
break;
}
}
});
$scope.toggleDistractionFree = function() {
function launchIntoFullscreen(element) {
if(element.requestFullscreen) {
element.requestFullscreen();
} else if(element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if(element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if(element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
function exitFullscreen() {
if(document.exitFullscreen) {
document.exitFullscreen();
} else if(document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if(document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
if(document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement) {
exitFullscreen();
} else {
launchIntoFullscreen(document.getElementById('app-content'));
}
};
$scope.$watch(function() {
return $scope.note.title;
}, function(newValue) {
if(newValue) {
document.title = newValue + ' - ' + $scope.defaultTitle;
} else {
document.title = $scope.defaultTitle;
}
});
});

102
js/app/controllers/notescontroller.js

@ -1,102 +0,0 @@
/**
* Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING file.
*/
// This is available by using ng-controller="NotesController" in your HTML
app.controller('NotesController', function($routeParams, $scope, $location,
Restangular, NotesModel, $window) {
'use strict';
$scope.route = $routeParams;
$scope.notesLoaded = false;
$scope.notes = NotesModel.getAll();
$scope.folderSelectorOpen = false;
$scope.filterCategory = null;
$scope.orderRecent = ['-favorite','-modified'];
$scope.orderAlpha = ['category','-favorite','title'];
$scope.filterOrder = $scope.orderRecent;
var notesResource = Restangular.all('notes');
// initial request for getting all notes
notesResource.getList().then(function (notes) {
NotesModel.addAll(notes);
$scope.notesLoaded = true;
});
$scope.create = function () {
notesResource.post({category: $scope.filterCategory})
.then(function (note) {
NotesModel.add(note);
$location.path('/notes/' + note.id);
});
};
$scope.delete = function (noteId) {
var note = NotesModel.get(noteId);
note.remove().then(function () {
NotesModel.remove(noteId);
$scope.$emit('$routeChangeError');
});
};
$scope.toggleFavorite = function (noteId, event) {
var note = NotesModel.get(noteId);
note.customPUT({favorite: !note.favorite},
'favorite', {}, {}).then(function (favorite) {
note.favorite = favorite ? true : false;
});
event.target.blur();
};
$scope.categories = [];
$scope.$watch('notes', function(notes) {
$scope.categories = NotesModel.getCategories(notes, 1, true);
}, true);
$scope.toggleFolderSelector = function () {
$scope.folderSelectorOpen = !$scope.folderSelectorOpen;
};
$scope.setFilter = function (category) {
if(category===null) {
$scope.filterOrder = $scope.orderRecent;
} else {
$scope.filterOrder = $scope.orderAlpha;
}
$scope.filterCategory = category;
$scope.folderSelectorOpen = false;
$('#app-navigation > ul').animate({scrollTop: 0}, 'fast');
};
$scope.categoryFilter = function (note) {
if($scope.filterCategory!==null) {
if(note.category===$scope.filterCategory) {
return true;
} else if(note.category!==null) {
return note.category.startsWith($scope.filterCategory+'/');
}
}
return true;
};
$scope.isCategory = function (item) {
return typeof item === 'string';
};
$window.onbeforeunload = function() {
var notes = NotesModel.getAll();
for(var i=0; i<notes.length; i+=1) {
if(notes[i].unsaved) {
return t('notes', 'There are unsaved notes. Leaving ' +
'the page will discard all changes!');
}
}
return null;
};
});

26
js/app/controllers/notessettingscontroller.js

@ -1,26 +0,0 @@
app.controller('NotesSettingsController',
function($scope, Restangular, $document) {
'use strict';
$scope.extensions = ['.txt', '.md'];
Restangular.one('settings').get().then(function(settings) {
if(angular.isObject(settings)) {
$scope.settings = settings;
} else {
$scope.settings = Restangular.one('settings');
}
});
$document.on('change', '#notesPath', function() {
var msg = t('notes', 'Please wait while new settings are applied…');
OC.Notification.show(msg);
$scope.settings.put().then(function() {
window.location.reload(true);
});
});
$document.on('change', '#fileSuffix', function() {
$scope.settings.put();
});
});

16
js/app/directives/autofocus.js

@ -1,16 +0,0 @@
/**
* Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING file.
*/
app.directive('notesAutofocus', function () {
'use strict';
return {
restrict: 'A',
link: function (scope, element) {
element.focus();
}
};
});

56
js/app/directives/editor.js

@ -1,56 +0,0 @@
/*global SimpleMDE*/
app.directive('editor', ['$timeout',
'urlFinder',
function ($timeout, urlFinder) {
'use strict';
return {
restrict: 'A',
link: function(scope, element) {
var simplemde = new SimpleMDE({
element: element[0],
spellChecker: false,
autoDownloadFontAwesome: false,
toolbar: false,
status: false,
forceSync: true
});
var editorElement = $(simplemde.codemirror.getWrapperElement());
simplemde.value(scope.note.content);
simplemde.codemirror.focus();
/* Initialize Checkboxes */
$('.CodeMirror-code').on('mousedown.checkbox touchstart.checkbox', '.cm-formatting-task', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
scope.toggleCheckbox(e.target);
});
simplemde.codemirror.on('update', function () {
// For strikethrough styling of completed tasks
$('.CodeMirror-line').removeClass('completed-task');
$('.CodeMirror-line:contains("[x]")').addClass('completed-task');
});
simplemde.codemirror.on('change', function() {
$timeout(function() {
scope.$apply(function () {
scope.note.content = simplemde.value();
scope.onEdit();
scope.updateTitle();
});
});
});
editorElement.on('click', '.cm-link, .cm-url', function(event) {
if(event.ctrlKey) {
var url = urlFinder(this);
if(angular.isDefined(url)) {
window.open(url, '_blank');
}
}
});
}
};
}]);

25
js/app/directives/tooltip.js

@ -1,25 +0,0 @@
/**
* Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING file.
*/
app.directive('notesTooltip', function () {
'use strict';
return {
restrict: 'A',
link: function (scope, element) {
element.tooltip({'container': 'body'});
element.on('$destroy', function() {
element.tooltip('hide');
});
element.on('click', function() {
element.tooltip('hide');
});
}
};
});

14
js/app/filters/and.js

@ -1,14 +0,0 @@
/**
* filter by multiple words (AND operation)
*/
app.filter('and', ['$filter', function ($filter) {
'use strict';
return function (items, searchString) {
var searchValues = searchString.split(' ');
var filtered = items;
for(var i in searchValues) {
filtered = $filter('filter')(filtered, searchValues[i]);
}
return filtered;
};
}]);