├── .gitignore
├── Gruntfile.js
├── LICENSE
├── README.md
├── assets
├── js
│ ├── app.js
│ ├── config.sample.js
│ ├── controllers
│ │ ├── appFrameCtrl.js
│ │ ├── bookCtrl.js
│ │ ├── booksCtrl.js
│ │ ├── editorCtrl.js
│ │ ├── evernoteCtrl.js
│ │ ├── favCtrl.js
│ │ ├── friendsCtrl.js
│ │ ├── infoCtrl.js
│ │ ├── loginCtrl.js
│ │ ├── module.js
│ │ └── noteCtrl.js
│ ├── directives.js
│ ├── douban-auth.js
│ ├── enml.js
│ ├── evernote-auth.js
│ ├── filters.js
│ ├── services
│ │ ├── analyticsService.js
│ │ ├── authService.js
│ │ ├── bookService.js
│ │ ├── evernoteService.js
│ │ ├── favouriteService.js
│ │ ├── filesystemService.js
│ │ ├── friendsService.js
│ │ ├── httpLoadingIntercepter.js
│ │ ├── httpOAuthIntercepter.js
│ │ ├── noteService.js
│ │ ├── serializeService.js
│ │ ├── translateService.js
│ │ └── userService.js
│ └── ui-directives.js
├── lib
│ ├── angular-bootstrap
│ │ ├── ui-bootstrap-tpls.js
│ │ └── ui-bootstrap.js
│ ├── angular
│ │ ├── angular-cookies.js
│ │ ├── angular-resource.js
│ │ ├── angular-route.js
│ │ └── angular.js
│ ├── codemirror
│ │ └── codemirror.js
│ ├── color-thief
│ │ └── color-thief.js
│ ├── marked
│ │ └── marked.js
│ ├── ng-infinite-scroll
│ │ └── ng-infinite-scroll.js
│ └── underscore
│ │ └── underscore.js
├── resource
│ ├── background.js
│ ├── css
│ │ ├── codemirror.css
│ │ └── themes
│ │ │ ├── solarized_dark.css
│ │ │ ├── tomorrow.css
│ │ │ └── zenburn.css
│ ├── fonts
│ │ ├── icomoon.eot
│ │ ├── icomoon.json
│ │ ├── icomoon.svg
│ │ ├── icomoon.ttf
│ │ └── icomoon.woff
│ ├── img
│ │ ├── books.png
│ │ └── login_with_douban_32.png
│ └── manifest.json
└── stylus
│ ├── edit.styl
│ ├── fonticon.styl
│ ├── lib
│ └── vendor.styl
│ ├── mod_about.styl
│ ├── mod_aside.styl
│ ├── mod_auth.styl
│ ├── mod_friends.styl
│ ├── mod_info.styl
│ ├── note.styl
│ ├── style.styl
│ ├── ui
│ ├── alert.styl
│ ├── bar.styl
│ ├── button.styl
│ ├── card.styl
│ ├── form.styl
│ ├── loading.styl
│ ├── modal.styl
│ ├── reset.styl
│ └── typography.styl
│ └── widgets
│ ├── book-3d.styl
│ ├── book.styl
│ ├── editor.styl
│ ├── note-card.styl
│ └── reader.styl
├── bower.json
├── package.json
├── public
├── background.js
├── css
│ ├── codemirror.css
│ ├── style.css
│ └── themes
│ │ ├── solarized_dark.css
│ │ ├── tomorrow.css
│ │ └── zenburn.css
├── fonts
│ ├── icomoon.eot
│ ├── icomoon.json
│ ├── icomoon.svg
│ ├── icomoon.ttf
│ └── icomoon.woff
├── img
│ ├── bg.png
│ ├── books.png
│ ├── fabric_of_squares_gray.png
│ └── login_with_douban_32.png
├── index.html
├── js
│ ├── app.min.js
│ ├── controllers.min.js
│ ├── directives.min.js
│ ├── douban-auth.min.js
│ ├── enml.min.js
│ ├── evernote-auth.min.js
│ ├── filters.min.js
│ ├── services.min.js
│ └── ui-directives.min.js
├── lib
│ ├── angular-bootstrap
│ │ ├── ui-bootstrap-tpls.min.js
│ │ └── ui-bootstrap.min.js
│ ├── angular
│ │ ├── angular-cookies.min.js
│ │ ├── angular-resource.min.js
│ │ ├── angular-route.min.js
│ │ ├── angular-sanitize.min.js
│ │ └── angular.min.js
│ ├── codemirror
│ │ └── codemirror.min.js
│ ├── color-thief
│ │ └── color-thief.min.js
│ ├── evernote
│ │ └── evernote-sdk.min.js
│ ├── marked
│ │ └── marked.min.js
│ ├── ng-infinite-scroll
│ │ └── ng-infinite-scroll.min.js
│ └── underscore
│ │ └── underscore.min.js
├── manifest.json
└── partials
│ ├── about.html
│ ├── book.html
│ ├── books.html
│ ├── edit.html
│ ├── error.html
│ ├── fav.html
│ ├── friends.html
│ ├── info.html
│ ├── login.html
│ ├── note.html
│ └── widgets
│ ├── editor.html
│ └── reader.html
└── views
├── index.jade
├── layout.jade
└── partials
├── about.jade
├── book.jade
├── books.jade
├── edit.jade
├── error.jade
├── fav.jade
├── friends.jade
├── info.jade
├── login.jade
├── note.jade
└── widgets
├── editor.jade
└── reader.jade
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | *~
3 |
4 | *.orig
5 | *.swp
6 |
7 | node_modules/
8 | bower_components/
9 |
10 |
11 | assets/js/config.js
12 | assets/js/controllers.js
13 | assets/js/services.js
14 | public/js/config.min.js
15 | public/css/style.css
16 | public/
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 NdYAG
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ANNO
2 |
3 | 
4 |
5 | ## 豆瓣读书笔记第三方客户端
6 |
7 | 这是一个基于Angular开发的Chrome App,可以独立安装在系统中。下载地址:[https://chrome.google.com/webstore/detail/读书笔记/cekpldeffalionmgoedldkdnlcbphamp](https://chrome.google.com/webstore/detail/读书笔记/cekpldeffalionmgoedldkdnlcbphamp)
8 |
9 | ## 基本功能
10 |
11 | 读书笔记的管理,写作,收藏,导出。
12 |
13 | ## 参与开发
14 |
15 | ANNO是一个开源的应用,目前基本支持了豆瓣的 OAuth2 读书笔记读写 API,还有以下功能正在开发:
16 |
17 | * 加入数学公式支持
18 | * ~~带格式导出笔记到Evernote~~ (已集成)
19 | * ……
20 |
21 | ### 开发准备
22 |
23 | 以下操作都在项目目录下执行
24 |
25 | * `npm install` 安装 Grunt 等依赖
26 | * `bower install` 安装前端使用的库文件(如果不需要使用到新的库文件,这一步和下一步可以忽略)
27 | * 将`bower_components/`中所需文件移到 `assets/lib/` 中
28 | * 申请豆瓣开发者帐号 [http://developers.douban.com/](http://developers.douban.com/)
29 | * 复制一份 `assets/config.sample.js` 到 `assets/config.js` ,填写你的 `api_key` 和 `api_secret`
30 | * 开发时执行 `grunt watch`,将 `assets/` 下的文件编译、压缩到 `public/` 中。
31 |
32 | 在Chrome扩展中加载项目目录,开启应用调试。
33 |
--------------------------------------------------------------------------------
/assets/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // shortcut
4 | var $ = function(selector) {
5 | if (typeof selector === 'string') {
6 | return angular.element(document.querySelectorAll(selector))
7 | } else if (selector.nodeType === 1) {
8 | return angular.element(selector)
9 | } else {
10 | return null
11 | }
12 | }
13 |
14 | var app = angular.module('ANNO', [
15 | 'ngRoute',
16 | 'ANNO.controllers',
17 | 'ANNO.directives',
18 | 'ANNO.ui-directives',
19 | 'ui.bootstrap'
20 | ]).config(['$routeProvider', function($routeProvider) {
21 | $routeProvider.when('/login', {
22 | templateUrl: '/partials/login.html',
23 | controller: 'LoginCtrl'
24 | }).when('/', {
25 | templateUrl: '/partials/books.html',
26 | controller: 'BooksCtrl'
27 | }).when('/:uid/book/:bid', {
28 | templateUrl: '/partials/book.html',
29 | controller: 'BookCtrl'
30 | }).when('/:uid/book/:bid/new', {
31 | templateUrl: '/partials/edit.html',
32 | controller: 'EditorCtrl'
33 | }).when('/:uid/info', {
34 | templateUrl: '/partials/info.html',
35 | controller: 'InfoCtrl'
36 | }).when('/note/:nid', {
37 | templateUrl: '/partials/note.html',
38 | controller: 'NoteCtrl'
39 | }).when('/note/:nid/edit', {
40 | templateUrl: '/partials/edit.html',
41 | controller: 'EditorCtrl'
42 | }).when('/friends', {
43 | templateUrl: '/partials/friends.html',
44 | controller: 'FriendsCtrl'
45 | }).when('/fav', {
46 | templateUrl: '/partials/fav.html',
47 | controller: 'FavCtrl'
48 | }).when('/about', {
49 | templateUrl: '/partials/about.html'
50 | }).when('/:uid/', {
51 | templateUrl: '/partials/books.html',
52 | controller: 'BooksCtrl'
53 | }).otherwise({
54 | templateUrl: '/partials/error.html'
55 | })
56 | }])
57 | .config(['$httpProvider', function($httpProvider) {
58 | $httpProvider.interceptors.push('HttpLoadingIntercepter')
59 | $httpProvider.interceptors.push('HttpOAuthIntercepter')
60 | }])
61 | .config(['$compileProvider', function($compileProvider) {
62 | $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|blob):|data:image\//)
63 | }])
64 | .run(function($route, $rootScope, $location, UserService) {
65 | $rootScope.$on( "$routeChangeStart", function(event, next, current) {
66 | if (next.templateUrl == "/partials/about.html" || next.templateUrl == "/partials/login.html") {
67 | return
68 | }
69 | UserService.isLoggedIn().catch(function() {
70 | $location.path('/login')
71 | })
72 | })
73 | })
74 |
--------------------------------------------------------------------------------
/assets/js/config.sample.js:
--------------------------------------------------------------------------------
1 | var DoubanAuthConf = {
2 | "api_key": "",
3 | "api_secret": "",
4 | "scopes": "douban_basic_common,book_basic_r,book_basic_w"
5 | }
6 | var EvernoteAuthConf = {
7 | "consumer_key": "",
8 | "consumer_secret": "",
9 | "hostname": {
10 | "evernote": "https://www.evernote.com",
11 | "yinxiang": "https://app.yinxiang.com"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/assets/js/controllers/appFrameCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('appFrameCtrl', ['$scope', '$rootScope', '$location', '$modal', '$compile', 'UserService', 'AnalyticsService', function($scope, $rootScope, $location, $modal, $compile, UserService, AnalyticsService) {
2 | $scope.asideVisible = 0
3 | $scope.toggleSidebar = function() {
4 | $scope.asideVisible ^= 1
5 | }
6 | $scope.$on('$routeChangeStart', function() {
7 | $scope.lastBook = null
8 | $scope.globalTitle = ''
9 | })
10 | $scope.$on('nav:lastBook', function(e, author, book) {
11 | $scope.author = author
12 | $scope.lastBook = book
13 | })
14 | $scope.$on('page:change', function(e, mode, title) {
15 | $scope.headerInvisible = (mode == 'note' || mode == 'editor')
16 | $scope.globalCSSClass = 'global_' + mode
17 | $scope.globalTitle = title || ''
18 | AnalyticsService.sendAppView(mode)
19 | })
20 |
21 | $scope.logout = function() {
22 | var modalInstance = $modal.open({
23 | templateUrl: 'modalLogout.html',
24 | controller: function($scope, $modalInstance) {
25 | $scope.ok = function (bid) {
26 | UserService.logout(function() {
27 | $location.path('/login')
28 | $modalInstance.close()
29 | })
30 | }
31 | $scope.cancel = function () {
32 | $modalInstance.dismiss('cancel')
33 | }
34 | }
35 | })
36 | }
37 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/bookCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('BookCtrl', ['$scope', '$routeParams', '$modal', '$timeout', 'UserService', 'SerializeService', 'TranslateService', 'FileSystemService', function($scope, $routeParams, $modal, $timeout, UserService, SerializeService, TranslateService, FileSystemService) {
2 |
3 | var books, book, notes
4 | , uid = $routeParams.uid
5 | , bid = $routeParams.bid
6 | UserService.login().then(function(user) {
7 | $scope.user = user
8 | })
9 | UserService.getUser(uid).then(function(user) {
10 | // retrieve book
11 | $scope.dominateColor = {
12 | background: '#fff',
13 | foreground: '#000'
14 | }
15 | $scope.author = user
16 | SerializeService.fetchBook(user.id, bid).then(function(book) {
17 | $scope.$emit('page:change', 'book', (user.is_self? '我的书架 ❯ ': '' + user.name + '的书架 ❯ ') + book.title)
18 | $scope.book = book
19 | $scope.order = 'time'
20 | $scope.reverse = true
21 | $scope.book_cover = book.images.large
22 | })
23 |
24 |
25 | $scope.book_opened = 0
26 | $scope.openBook = function() {
27 | $scope.book_opened = 1
28 | }
29 | $scope.closeBook = function() {
30 | $scope.book_opened = 0
31 | }
32 | $scope.book_sided = false
33 | $scope.$watch('dominateColor', function(colors) {
34 | if (colors.background != '#fff') { // after processed by color thief, background will be rgb. Thus #fff means color thief has not stealed the color
35 | $scope.book_sided = true
36 | }
37 | })
38 |
39 | $scope.exportNotes = function() {
40 | SerializeService.fetchBook(user.id, bid).then(function(book) {
41 | var exports
42 | exports = book.notes.map(function(note) {
43 | return (note.chapter? ('#' + note.chapter + '\n\n') : '')
44 | + TranslateService.doubanToMarkdown(note.content, note.photos)
45 | }).join('\n\n-----\n\n')
46 |
47 | var modalInstance = $modal.open({
48 | templateUrl: 'modalExport.html',
49 | controller: function($scope, $modalInstance) {
50 | $scope.download = function () {
51 | if ($scope.exportFormat == 'markdown') {
52 | var blob = new Blob([exports], {type: "text/plain;charset=utf-8"})
53 | // saveAs(blob, book.title + ".md")
54 | FileSystemService.save(blob, book.title, 'md', function(is_saved) {
55 | is_saved && $modalInstance.close()
56 | })
57 | } else if ($scope.exportFormat == 'html') {
58 | var el = angular.element(document.querySelector('.preview-html')).clone()
59 | Array.prototype.slice.call(el.find('img'), 0).forEach(function(img) {
60 | img = $(img)
61 | img.attr('src', img.attr('data-src'))
62 | img.removeAttr('data-src')
63 | })
64 | var blob = new Blob([el.html()], {type: "text/html;charset=utf-8"})
65 | // saveAs(blob, book.title + '.html')
66 | FileSystemService.save(blob, book.title, 'html', function(is_saved) {
67 | is_saved && $modalInstance.close()
68 | })
69 | }
70 | }
71 | $scope.cancel = function () {
72 | $modalInstance.dismiss('cancel')
73 | }
74 | $scope.exports = exports
75 | $scope.exportFormat = 'markdown'
76 | $scope.changeExportFormat = function(mode) {
77 | $scope.exportFormat = mode
78 | }
79 |
80 | $scope.popupEvernote = function() {
81 | $modal.open({
82 | templateUrl: 'modalEvernote.html',
83 | controller: 'EvernoteCtrl',
84 | resolve: {
85 | title: function() {
86 | return book.title
87 | }
88 | },
89 | windowClass: 'mask-evernote'
90 | })
91 | }
92 |
93 | }
94 | })
95 |
96 | })
97 | }
98 | })
99 |
100 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/booksCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('BooksCtrl', ['$scope', '$routeParams', '$modal', '$timeout', 'UserService', 'SerializeService', function($scope, $routeParams, $modal, $timeout, UserService, SerializeService) {
2 |
3 | // Save user data after login
4 | var uid = $routeParams.uid
5 |
6 | UserService.getUser(uid).then(function(user) {
7 | $scope.user = user
8 | $scope.$emit('page:change', 'books', (user.name || '我') + '的书架')
9 | // fetch books
10 | SerializeService.fetchAllBooks(user.id).then(function(books) {
11 | $scope.books = books
12 | $scope.tipVisible = !user.is_self && _.isEmpty(books)
13 | })
14 |
15 | $scope.searchBook = function() {
16 | var modalInstance = $modal.open({
17 | templateUrl: 'modalSearchBook.html',
18 | controller: 'SearchBookCtrl',
19 | resolve: {
20 | 'user': function() {
21 | return user
22 | }
23 | }
24 | }).opened.then(function() {
25 | $timeout(function() {
26 | // not that way angular
27 | var firstInput = $('.modal-dialog input[type="text"]')
28 | if (firstInput.length) {
29 | firstInput[0].focus()
30 | }
31 | }, 50)
32 | })
33 | }
34 | })
35 |
36 | }]).controller('SearchBookCtrl', ['$scope', '$modalInstance', '$location', '$http', 'BookService', 'user', function($scope, $modalInstance, $location, $http, BookService, user) {
37 | $scope.searchBook = function(query) {
38 | if (!query.match(/^\d*$/)) { // search by keyword
39 | $http.get('/api/v2/book/search', {
40 | params: {
41 | q: query
42 | }
43 | }).success(function(res) {
44 | $scope.books = res.books
45 | if (res.books.length) {
46 | $scope.selectedIndex = 0
47 | $scope.selectedBook = res.books[0]
48 | }
49 | })
50 | } else { // search by subject id
51 | BookService.get(query).then(function(book) {
52 | $scope.selectedIndex = 0
53 | $scope.selectedBook = book
54 | $scope.books = [book]
55 | })
56 | }
57 | $scope.has_searched = true
58 | }
59 | $scope.selectBook = function($index, book) {
60 | $scope.selectedIndex = $index
61 | $scope.selectedBook = book
62 | }
63 | $scope.ok = function (bid) {
64 | $location.path('/' + user.uid + '/book/' + bid +'/new')
65 | $modalInstance.close()
66 | }
67 | $scope.cancel = function () {
68 | $modalInstance.dismiss('cancel')
69 | }
70 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/editorCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('EditorCtrl', ['$scope', '$routeParams', '$http', '$location', 'UserService', 'BookService', 'NoteService', 'TranslateService', function($scope, $routeParams, $http, $location, UserService, BookService, NoteService, TranslateService) {
2 | $scope.$emit('page:change', 'editor')
3 |
4 | UserService.login().then(function(user) {
5 | var note
6 | if ($routeParams.nid) { // update note
7 | var nid = $routeParams.nid
8 |
9 | $scope.action = 'put'
10 |
11 | $scope.note = NoteService.get({
12 | id: nid
13 | }).success(function(resp) {
14 | note = _.pick(resp, 'id', 'chapter', 'page_no', 'privacy', 'content', 'book_id', 'photos', 'last_photo', 'book')
15 | note.page_no = note.page_no || null // api returns 0, turn it to null
16 | note.content = TranslateService.doubanToMarkdown(note.content, note.photos)
17 | note.privacy = note.privacy == '2'? 'public': 'private'
18 | $scope.note = note
19 | $scope.$emit('nav:lastBook', user, _.pick(resp.book, 'id', 'title'))
20 | })
21 | } else if ($routeParams.bid) { // create new note
22 | $scope.note = {
23 | chapter: '',
24 | content: '',
25 | page_no: null,
26 | photos: {},
27 | privacy: 'public'
28 | }
29 | $scope.action = 'post'
30 | BookService.get($routeParams.bid).then(function(book) {
31 | $scope.note.book = book
32 | $scope.$emit('nav:lastBook', user, book)
33 | })
34 | }
35 | })
36 |
37 | $scope.mode = 'column-mode'
38 | $scope.changeMode = function(mode) {
39 | $scope.mode = mode
40 | }
41 |
42 | $scope.is_fullscreen = 0
43 | $scope.fullscreen = function() {
44 | if ($scope.is_fullscreen) {
45 | chrome.app.window.current().restore()
46 | } else {
47 | chrome.app.window.current().fullscreen()
48 | }
49 | $scope.is_fullscreen ^= 1
50 | }
51 |
52 | $scope.images = []
53 | $scope.save = function() {
54 | var payload = {
55 | content: TranslateService.markdownToDouban($scope.note.content),
56 | chapter: $scope.note.chapter,
57 | privacy: $scope.note.privacy
58 | }
59 | , last_photo = $scope.note.last_photo
60 | $scope.note.page_no && (payload['page'] = $scope.note.page_no)
61 | if ($scope.action == 'put') {
62 | NoteService.put({
63 | id: $routeParams.nid
64 | }, payload, $scope.images).success(function(note) {
65 | $location.path('/note/' + note.id)
66 | })
67 | } else if ($scope.action == 'post') {
68 | NoteService.post({
69 | bid: $routeParams.bid
70 | }, payload, $scope.images).success(function(note) {
71 | $location.path('/note/' + note.id)
72 | })
73 | }
74 | }
75 |
76 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/evernoteCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('EvernoteCtrl', ['$scope', '$rootScope', '$modalInstance', 'EvernoteService', 'title', function($scope, $rootScope, $modalInstance, EvernoteService, title) {
2 | var STATUS = {
3 | 'NOTEBOOK': {
4 | 'PENDING': '正在获取Evernote笔记本列表...',
5 | 'SUCCESS': '',
6 | 'FAIL': '与Evernote服务器通信失败,请检查Internet是否正常。'
7 | },
8 | 'NOTE': {
9 | 'PENDING': '正在保存',
10 | 'SUCCESS': '',
11 | 'FAIL': '保存失败,请检查Internet是否正常,或者关闭窗口重试一次。'
12 | }
13 | }
14 | $scope.status = STATUS.NOTEBOOK.PENDING
15 | EvernoteService.listNoteBooks(function(res) {
16 | $scope.notebooks = Array.prototype.slice.call(res, 0) // [{name: ,quid:}]
17 | $scope.selectedNotebook = $scope.notebooks[0]
18 | $scope.status = STATUS.NOTEBOOK.SUCCESS
19 | $scope.$apply()
20 | }, function() {
21 | $scope.status = STATUS.NOTEBOOK.FAIL
22 | $scope.$apply()
23 | })
24 | $scope.choice_notebook = 'exist'
25 |
26 | function saveNoteTo(notebook) {
27 | EvernoteService.save(title
28 | , $('.content')
29 | , notebook.guid
30 | , function() {
31 | $rootScope.$broadcast('alert:success', '保存成功 :)')
32 | $modalInstance.close()
33 | }
34 | , function(msg) {
35 | $scope.status = STATUS.NOTE.FAIL + (msg && ('错误:' + msg + ' 告知开发者让他修复吧:)'))
36 | $scope.$apply()
37 | })
38 | }
39 | $scope.ok = function(choice, existbook, newbook) {
40 | if (choice == 'exist') {
41 | saveNoteTo(existbook)
42 | } else if (choice == 'new') {
43 | EvernoteService.createNoteBook(newbook, function(newbook) {
44 | saveNoteTo(newbook)
45 | })
46 | }
47 | }
48 | $scope.cancel = function() {
49 | $modalInstance.close()
50 | }
51 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/favCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('FavCtrl', ['$scope', 'FavouriteService', function($scope, FavouriteService) {
2 | $scope.$emit('page:change', 'fav', '我收藏的笔记*')
3 | FavouriteService.fetchAll(function(notes) {
4 | $scope.notes = notes
5 | })
6 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/friendsCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('FriendsCtrl', ['$scope', '$http', 'FriendsService', function($scope, $http, FriendsService) {
2 | $scope.$emit('page:change', 'friends', '友邻的笔记')
3 |
4 | $scope.showMoreFriends = function() {
5 | FriendsService.getMore().then(function(friends) {
6 | $scope.and_more = FriendsService.has_more
7 | })
8 | }
9 |
10 | $scope.friends = FriendsService.getAllLocal()
11 | $scope.and_more = FriendsService.has_more
12 |
13 | if (!$scope.friends.length) {
14 | $scope.showMoreFriends()
15 | }
16 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/infoCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('InfoCtrl', ['$scope', 'AuthService', 'UserService', 'SerializeService', 'AnalyticsService', function($scope, AuthService, UserService, SerializeService, AnalyticsService) {
2 | $scope.$emit('page:change', 'info', '个人信息')
3 |
4 | UserService.getUserInfo().then(function(user) {
5 | $scope.user = user
6 |
7 | SerializeService.fetchAll(user.id).then(function(resp) {
8 | var books = resp[0]
9 | , notes = resp[1]
10 |
11 | $scope.total_books = _.size(books)
12 | var max = 0, max_id
13 | _.each(books, function(book, bid) {
14 | if (book.notes_count > max) {
15 | max = book.notes_count
16 | max_id = bid
17 | }
18 | })
19 | $scope.max_book = books[max_id]
20 |
21 | $scope.total_notes = notes.length
22 | var start, start_bid, start_nid
23 | , end, end_id
24 | , time
25 | start = end = new Date(notes[0].time)
26 | start_bid = notes[0].book_id
27 | start_nid = notes[0].id
28 | _.each(notes, function(note, nid) {
29 | time = new Date(note.time)
30 | if (time < start) {
31 | start = time
32 | start_bid = note.book_id
33 | start_nid = nid
34 | }
35 | if (time > end) {
36 | end = time
37 | end_id = nid
38 | }
39 | })
40 | $scope.start = start.toLocaleDateString()
41 | $scope.end = end.toLocaleDateString()
42 | $scope.start_book = books[start_bid]
43 | $scope.start_note = notes[start_nid]
44 |
45 | // find first five unique
46 | var note_group = _.groupBy(notes, function(note) { return note.book_id })
47 | $scope.recent_books = _.map(_.sortBy(_.values(note_group), function(notes) {return new Date(notes[0].time) }).reverse().slice(0,5), function(notes) {return books[notes[0].book_id] })
48 |
49 | })
50 | })
51 |
52 | AuthService.isEvernoteAuthed().then(function(type) {
53 | $scope[type + '_bound'] = true
54 | })
55 | $scope.authEvernote = function(type) {
56 | // type: evernote or yinxiang
57 | AuthService.evernote(type, function() {
58 | $scope[type + '_bound'] = true
59 | $scope.$apply()
60 | })
61 | }
62 | $scope.unbind = function(type) {
63 | AuthService.unbindEvernote(type, function() {
64 | $scope[type + '_bound'] = false
65 | $scope.$apply()
66 | })
67 | }
68 |
69 | AnalyticsService.isTrackingPermitted().then(function(isTrackingPermitted) {
70 | $scope.analytics_allowed = isTrackingPermitted
71 | })
72 | $scope.alter_analytics = function(isTrackingPermitted) {
73 | AnalyticsService.setTrackingPermission($scope.analytics_allowed)
74 | }
75 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/loginCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('LoginCtrl', ['$scope', '$window', 'AuthService', function($scope, $window, AuthService) {
2 | $scope.reload = function() {
3 | $window.location.href = '/auth'
4 | }
5 | $scope.getToken = AuthService.auth
6 | }])
--------------------------------------------------------------------------------
/assets/js/controllers/module.js:
--------------------------------------------------------------------------------
1 | var annoCtrl = angular.module('ANNO.controllers', ['infinite-scroll'])
2 |
--------------------------------------------------------------------------------
/assets/js/controllers/noteCtrl.js:
--------------------------------------------------------------------------------
1 | annoCtrl.controller('NoteCtrl', ['$scope', '$http', '$routeParams', '$modal', '$location', 'UserService', 'NoteService', 'TranslateService', 'FavouriteService', 'EvernoteService', function($scope, $http, $routeParams, $modal, $location, UserService, NoteService, TranslateService, FavouriteService, EvernoteService) {
2 | $scope.$emit('page:change', 'note')
3 |
4 | var note
5 | , nid = $routeParams.nid
6 |
7 | UserService.login().then(function(user) {
8 | NoteService.get({
9 | id: nid
10 | }).success(function(resp) {
11 | note = _.pick(resp, 'id', 'chapter', 'page_no', 'privacy', 'summary', 'content', 'photos', 'last_photo')
12 | note.content = TranslateService.doubanToMarkdown(note.content, note.photos)
13 | $scope.note = note
14 | $scope.book = _.pick(resp.book, 'id', 'title')
15 |
16 | $scope.author = resp.author_user
17 |
18 | $scope.can_operate = (resp.author_id == user.id)
19 |
20 | $scope.$emit('nav:lastBook', $scope.author, $scope.book)
21 |
22 | FavouriteService.getState(note, function(state) {
23 | $scope.has_starred = state
24 | $scope.$apply()
25 | })
26 | $scope.toggleStar = function(state) {
27 | FavouriteService[state? 'star': 'unstar'](_.omit(note, 'content'), function(state) {
28 | $scope.has_starred = state.is_starred
29 | $scope.$apply()
30 | })
31 | }
32 | $scope.popupEvernote = function() {
33 | var modalInstance = $modal.open({
34 | templateUrl: 'modalEvernote.html',
35 | controller: 'EvernoteCtrl',
36 | resolve: {
37 | title: function() {
38 | return (note.chapter || (book.title + '第' + note.page_no + '页'))
39 | }
40 | }
41 | })
42 | }
43 | }).catch(function(error) {
44 | $scope.error = error
45 | })
46 |
47 | })
48 | function deleteNote(id, bid) {
49 | return NoteService.remove({
50 | id: id,
51 | bid: $scope.book.id
52 | }).success(function() {
53 | $location.path('/' + $scope.user.uid + '/book/' + $scope.book.id)
54 | })
55 | }
56 | $scope.open = function() {
57 | var modalInstance = $modal.open({
58 | templateUrl: 'modalDeleteNote.html',
59 | controller: function($scope, $modalInstance) {
60 | $scope.ok = function () {
61 | deleteNote(nid)
62 | $modalInstance.close()
63 | }
64 | $scope.cancel = function () {
65 | $modalInstance.dismiss('cancel')
66 | }
67 | },
68 | resolve: function() {
69 | }
70 | })
71 |
72 | modalInstance.result.then(function(selectedItem){
73 | $scope.selected = selectedItem
74 | })
75 | }
76 | }])
--------------------------------------------------------------------------------
/assets/js/douban-auth.js:
--------------------------------------------------------------------------------
1 | var DoubanAuth = (function() {
2 | 'use strict'
3 |
4 | var conf = DoubanAuthConf
5 | , redirectURL = 'https://' + chrome.runtime.id + '.chromiumapp.org/auth/callback'
6 | , redirectRe = new RegExp(redirectURL + '[#\?](.*)')
7 | , access_token = null
8 | , userinfo
9 |
10 | return {
11 | getToken: function(callback) {
12 | if (access_token) {
13 | callback(null, access_token)
14 | return
15 | }
16 | var options = {
17 | interactive: true,
18 | url: 'https://www.douban.com/service/auth2/auth?client_id=' + conf.api_key +
19 | '&response_type=code' +
20 | '&redirect_uri=' + encodeURIComponent(redirectURL) +
21 | '&scope=' + conf.scopes
22 | }
23 | chrome.identity.launchWebAuthFlow(options, function(redirectUri) {
24 | console.log('launchWebAuthFlow completed', chrome.runtime.lastError, redirectUri)
25 | if (chrome.runtime.lastError) {
26 | callback(new Error(chrome.runtime.lastError))
27 | return
28 | }
29 | var matches = redirectUri.match(redirectRe)
30 | if (matches && matches.length > 1)
31 | handleProviderResponse(parseRedirectFragment(matches[1]))
32 | else
33 | callback(new Error('Invalid redirect URI'))
34 |
35 | })
36 | function parseRedirectFragment(fragment) {
37 | var pairs = fragment.split(/&/);
38 | var values = {};
39 |
40 | pairs.forEach(function(pair) {
41 | var nameval = pair.split(/=/);
42 | values[nameval[0]] = nameval[1];
43 | });
44 |
45 | return values;
46 | }
47 |
48 | function handleProviderResponse(values) {
49 | console.log('providerResponse', values);
50 | if (values.hasOwnProperty('access_token'))
51 | setAccessToken(values.access_token);
52 | // If response does not have an access_token, it might have the code,
53 | // which can be used in exchange for token.
54 | else if (values.hasOwnProperty('code'))
55 | exchangeCodeForToken(values.code);
56 | else
57 | callback(new Error('Neither access_token nor code avialable.'));
58 | }
59 |
60 | function exchangeCodeForToken(code) {
61 | var xhr = new XMLHttpRequest();
62 | xhr.open('POST',
63 | 'https://www.douban.com/service/auth2/token?' +
64 | 'client_id=' + conf.api_key +
65 | '&client_secret=' + conf.api_secret +
66 | '&redirect_uri=' + redirectURL +
67 | '&grant_type=authorization_code' +
68 | '&code=' + code);
69 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
70 | xhr.setRequestHeader('Accept', 'application/json');
71 | xhr.onload = function () {
72 | // When exchanging code for token, the response comes as json, which
73 | // can be easily parsed to an object.
74 | if (this.status === 200) {
75 | var response = JSON.parse(this.responseText);
76 | console.log(response);
77 | if (response.hasOwnProperty('access_token')) {
78 | setAccessToken(response.access_token);
79 | } else {
80 | callback(new Error('Cannot obtain access_token from code.'));
81 | }
82 | } else {
83 | console.log('code exchange status:', this.status);
84 | callback(new Error('Code exchange failed'));
85 | }
86 | };
87 | xhr.send();
88 | }
89 | function setAccessToken(token) {
90 | access_token = token;
91 | console.log('Setting access_token: ', access_token);
92 | callback(null, access_token);
93 | }
94 |
95 | }
96 | }
97 |
98 | })()
--------------------------------------------------------------------------------
/assets/js/enml.js:
--------------------------------------------------------------------------------
1 | // require $ alias of angular.element
2 | // todo: put this to translateService...
3 | // extract style
4 | // html to xhmlt
to
, hr to
, img to
5 | var enml = (function() {
6 | var EXTRACT_RULE = {
7 | 'blockquote': ['border-left'],
8 | 'span': ['color'],
9 | 'code': ['border', 'background-color'],
10 | 'pre': ['background-color', 'margin']
11 | }
12 |
13 | function html2enml(node) {
14 | var $$ = angular.element
15 | var clone = node.cloneNode(true)
16 | , process = $$('')
17 | , nodeClassName = $(node).attr('class').split(' ')[0]
18 | , result
19 | $('body').append(process)
20 | process = $('#process')
21 | process.append($(clone))
22 | nodewalk(clone.childNodes, process_node)
23 |
24 | result = $('#process .' + nodeClassName).html()
25 | .replace(/
/g, '
')
26 | .replace(/
/g, '
')
27 | .replace(/
/g, '
')
28 | process.remove()
29 | return result
30 | }
31 |
32 | function nodewalk(node, fn) {
33 | for (var i = 0; i < node.length; i++) {
34 | if (node[i].hasChildNodes()) { // todo: filter by node.nodeName
35 | nodewalk(node[i].childNodes, fn)
36 | }
37 | if (1 === node[i].nodeType) { // element
38 | fn(node[i])
39 | }
40 | }
41 | }
42 |
43 | function process_node(node) {
44 | if (node.nodeName == "IMG") {
45 | node.removeAttribute('src')
46 | node.removeAttribute('alt')
47 | }
48 | style_inline(node)
49 | node.removeAttribute('class')
50 | node.removeAttribute('id')
51 | }
52 |
53 | // example: style="color: red; background: green;"
54 | function style_inline(node) {
55 | var nodename = node.nodeName.toLowerCase()
56 | , rules
57 | , str_style
58 | if (nodename in EXTRACT_RULE) {
59 | rules = EXTRACT_RULE[nodename]
60 | str_style = ''
61 | for(var i = 0, l = rules.length, rule; i < l; i++) {
62 | rule = rules[i]
63 | str_style += rule + ':' +
64 | getComputedStyle(node).getPropertyValue(rule) + ';'
65 | }
66 | node.setAttribute('style', str_style)
67 | }
68 | }
69 | return {
70 | html2enml: html2enml
71 | }
72 | })()
73 |
--------------------------------------------------------------------------------
/assets/js/evernote-auth.js:
--------------------------------------------------------------------------------
1 | var EvernoteAuth = (function() {
2 | 'use strict'
3 |
4 | var conf = EvernoteAuthConf
5 | , redirectURL = 'https://' + chrome.runtime.id + '.chromiumapp.org/auth/callback'
6 | , redirectRe = new RegExp(redirectURL + '[#\?](.*)')
7 | , access_token = null
8 | , userinfo
9 |
10 | return {
11 | getToken: function(type, callback) {
12 | if (access_token) {
13 | callback(null, access_token)
14 | return
15 | }
16 | var options = {
17 | interactive: true,
18 | url: conf.hostname[type] + '/oauth?oauth_consumer_key=' + conf.consumer_key +
19 | '&oauth_signature=' + conf.consumer_secret + "%26" +
20 | '&oauth_signature_method=PLAINTEXT' +
21 | '&oauth_timestamp=' + (new Date).valueOf() +
22 | '&oauth_nonce' + (new Date).valueOf() +
23 | '&oauth_callback=' + encodeURIComponent(redirectURL)
24 | }
25 | // oauth step1: get temporary credential
26 | var xhr = new XMLHttpRequest()
27 | xhr.open('GET', options.url)
28 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
29 | xhr.onload = function() {
30 | if (this.status === 200) {
31 | var resp = parseRedirectFragment(this.responseText)
32 | console.log(resp)
33 | if (!resp.oauth_callback_confirmed) return
34 | chrome.identity.launchWebAuthFlow({
35 | interactive: true,
36 | url: conf.hostname[type] + '/OAuth.action?oauth_token=' + resp.oauth_token
37 | }, function(redirectUri) {
38 | console.log('launchWebAuthFlow completed', chrome.runtime.lastError, redirectUri)
39 | if (chrome.runtime.lastError) {
40 | callback(new Error(chrome.runtime.lastError))
41 | return
42 | }
43 | var matches = redirectUri.match(redirectRe)
44 | if (matches && matches.length > 1)
45 | handleProviderResponse(resp, parseRedirectFragment(redirectUri))
46 | else
47 | callback(new Error('Invalid redirect URI'))
48 | })
49 | }
50 | }
51 | xhr.send()
52 |
53 | function parseRedirectFragment(fragment, options) {
54 | var pairs = fragment.split(/&/);
55 | var values = {};
56 |
57 | pairs.forEach(function(pair) {
58 | var nameval = pair.split(/=/);
59 | if (options && options.decode) {
60 | values[nameval[0]] = decodeURIComponent(nameval[1]);
61 | } else {
62 | values[nameval[0]] = nameval[1];
63 | }
64 | });
65 |
66 | return values;
67 | }
68 |
69 | function handleProviderResponse(values, values2) {
70 | console.log('providerResponse', values);
71 | if (values.hasOwnProperty('access_token'))
72 | setAccessToken(values.access_token);
73 | // oauth step2: exhange temporary credentials for token
74 | else if (values.hasOwnProperty('oauth_token'))
75 | exchangeCodeForToken(values.oauth_token, values2.oauth_verifier);
76 | else
77 | callback(new Error('Neither access_token nor code avialable.'));
78 | }
79 |
80 | function exchangeCodeForToken(token, verifier) {
81 | var xhr = new XMLHttpRequest();
82 | xhr.open('POST',
83 | conf.hostname[type] + '/oauth?' +
84 | 'oauth_consumer_key=' + conf.consumer_key +
85 | '&oauth_signature=' + conf.consumer_secret + "%26" +
86 | '&oauth_signature_method=PLAINTEXT' +
87 | '&oauth_timestamp=' + (new Date).valueOf() +
88 | '&oauth_nonce=' + (new Date).valueOf() +
89 | '&oauth_token=' + token +
90 | '&oauth_verifier=' + verifier);
91 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
92 | xhr.onload = function () {
93 | if (this.status === 200) {
94 | // var response = JSON.parse(this.responseText);
95 | var response = parseRedirectFragment(this.responseText, {decode: true});
96 | console.log(response);
97 | if (response.hasOwnProperty('oauth_token')) {
98 | callback(null, response)
99 | // setAccessToken(response.oauth_token);
100 | // save notestore url
101 | // save(decodeURIComponent(response.edam_noteStoreUrl))
102 | } else {
103 | callback(new Error('Cannot obtain access_token from code.'));
104 | }
105 | } else {
106 | console.log('code exchange status:', this.status);
107 | callback(new Error('Code exchange failed'));
108 | }
109 | };
110 | xhr.send();
111 | }
112 | // function setAccessToken(token) {
113 | // access_token = token;
114 | // console.log('Setting access_token: ', access_token);
115 | // callback(null, access_token);
116 | // }
117 |
118 | }
119 | }
120 |
121 | })()
--------------------------------------------------------------------------------
/assets/js/filters.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | angular.module('ANNO')
4 | .filter('characters', function() {
5 | return function(input, chars) {
6 | if (input && input.length >= chars) {
7 | input = input.substring(0, chars)
8 |
9 | var lastspace = input.lastIndexOf(' ')
10 | if (lastspace !== -1) {
11 | input = input.substr(0, lastspace)
12 | }
13 | return input + '...'
14 | }
15 | return input
16 | }
17 | })
--------------------------------------------------------------------------------
/assets/js/services/analyticsService.js:
--------------------------------------------------------------------------------
1 | app.factory('AnalyticsService', ['$q', function($q) {
2 | var service = analytics.getService(GAConf.service)
3 | , tracker = service.getTracker(GAConf.tracker)
4 |
5 | var configDefer = new $q.defer()
6 | service.getConfig().addCallback(function(config) {
7 | configDefer.resolve(config)
8 | })
9 | return {
10 | isTrackingPermitted: function() {
11 | var defer = new $q.defer()
12 | configDefer.promise.then(function(config) {
13 | defer.resolve(config.isTrackingPermitted())
14 | })
15 | return defer.promise
16 | },
17 | setTrackingPermission: function(isTrackingPermitted) {
18 | configDefer.promise.then(function(config) {
19 | config.setTrackingPermitted(isTrackingPermitted)
20 | })
21 | },
22 | sendAppView: function(view) {
23 | tracker.sendEvent('ChangeView', 'Change', view)
24 | },
25 | reportError: function(error) {
26 | tracker.sendEvent('Error', String(error.id), error.msg)
27 | }
28 | }
29 | }])
--------------------------------------------------------------------------------
/assets/js/services/authService.js:
--------------------------------------------------------------------------------
1 | app.factory('AuthService', ['$rootScope', '$http', '$q', '$location', function($rootScope, $http, $q, $location) {
2 | return {
3 | auth: function() {
4 | DoubanAuth.getToken(function(error, access_token) {
5 | if (error) return
6 | $http({
7 | method: 'GET',
8 | url: 'https://api.douban.com/v2/user/~me',
9 | headers: {
10 | 'Authorization': 'Bearer ' + access_token
11 | }
12 | }).success(function(resp) {
13 | chrome.storage.local.set({'logintoken': {
14 | id: resp.id,
15 | uid: resp.uid,
16 | avatar: resp.large_avatar,
17 | token: access_token
18 | }})
19 | $location.path('/')
20 | })
21 |
22 | })
23 | },
24 | evernote: function(type, callback) {
25 | EvernoteAuth.getToken(type, function(error, res) {
26 | if (!res.oauth_token) {
27 | return
28 | }
29 | if ($rootScope.user) {
30 | // set and save
31 | var token = {}
32 | token[type] = res // type: evernote or yinxiang
33 | chrome.storage.local.set({'logintoken': _.extend($rootScope.user, token)})
34 | }
35 | callback && callback()
36 | })
37 | },
38 | unbindEvernote: function(type, callback) {
39 | if ($rootScope.user[type]) {
40 | delete $rootScope.user[type]
41 | }
42 | chrome.storage.local.set({ 'logintoken': $rootScope.user }, callback)
43 | },
44 | isEvernoteAuthed: function() {
45 | var defer = new $q.defer()
46 | , valid = function(conf) {
47 | return (conf && conf.oauth_token && (new Date) < conf.edam_expires)
48 | }
49 | chrome.storage.local.get('logintoken', function(res) {
50 | var evernote = res.logintoken.evernote
51 | , yinxiang = res.logintoken.yinxiang
52 | if (valid(evernote)) {
53 | defer.resolve('evernote')
54 | } else if (valid(yinxiang)) {
55 | defer.resolve('yinxiang')
56 | } else {
57 | defer.reject()
58 | }
59 | })
60 | return defer.promise
61 | }
62 | }
63 | }])
--------------------------------------------------------------------------------
/assets/js/services/bookService.js:
--------------------------------------------------------------------------------
1 | app.factory('BookService', ['$q', '$http', '$rootScope', function($q, $http, $rootScope) {
2 | // fetch book from sessionStorage or from $http
3 | return {
4 | get: function(bid) {
5 | var user = $rootScope.user
6 | , id = user.id
7 |
8 | var defer = $q.defer()
9 | var books = sessionStorage.getItem(id + '_books')
10 | , book
11 | if (books) {
12 | books = JSON.parse(books)
13 | book = books[bid]
14 | }
15 | if (!book) {
16 | $http.get('/api/v2/book/' + bid).success(function(book) {
17 | defer.resolve(_.pick(book, 'id', 'title', 'author', 'images', 'summary'))
18 | })
19 | } else {
20 | defer.resolve(book)
21 | }
22 | return defer.promise
23 | }
24 | }
25 | }])
--------------------------------------------------------------------------------
/assets/js/services/evernoteService.js:
--------------------------------------------------------------------------------
1 | app.factory('EvernoteService', ['$rootScope', function($rootScope) {
2 | var user = $rootScope.user
3 | , conf = user.evernote || user.yinxiang
4 |
5 | if (!conf) return {}
6 |
7 | var noteStoreURL = conf.edam_noteStoreUrl
8 | , authenticationToken = conf.oauth_token
9 | , noteStoreTransport = new Thrift.BinaryHttpTransport(noteStoreURL)
10 | , noteStoreProtocol = new Thrift.BinaryProtocol(noteStoreTransport)
11 | , noteStore = new NoteStoreClient(noteStoreProtocol)
12 | return {
13 | listNoteBooks: function(done, fail) {
14 | noteStore.listNotebooks(authenticationToken, function (notebooks) {
15 | done && done(notebooks)
16 | }, function onerror(error) {
17 | fail(error)
18 | })
19 | },
20 | createNoteBook: function(name, callback) {
21 | this.listNoteBooks(function(notebooks) {
22 | var notebook = _.filter(notebooks, function(nb) {
23 | return nb.name == name
24 | })
25 | if (!notebook.length) {
26 | var nnb = new Notebook()
27 | nnb.name = name
28 | noteStore.createNotebook(authenticationToken, nnb, function(res) {
29 | callback && callback(res)
30 | })
31 | } else {
32 | callback && callback(notebook[0])
33 | }
34 | })
35 | },
36 | save: function(title, content_node, notebook_id, done, fail) {
37 | var note = new Note
38 | note.title = title
39 | note.content = '' + enml.html2enml(content_node[0]) + ''
40 | note.notebookGuid = notebook_id
41 | noteStore.createNote(authenticationToken, note, function(res) {
42 | if (res.errorCode) {
43 | fail && fail(res.parameter)
44 | } else {
45 | done && done()
46 | }
47 | })
48 | }
49 | }
50 | }])
--------------------------------------------------------------------------------
/assets/js/services/favouriteService.js:
--------------------------------------------------------------------------------
1 | app.factory('FavouriteService', ['$rootScope', function($rootScope) {
2 | // use chrome.storage.sync to save data
3 | var storage = chrome.storage.sync
4 | , STORAGE_STAR = $rootScope.user.id + '_favourites'
5 | return {
6 | getState: function(note, cb) {
7 | this.fetchAll(function(favourites) {
8 | var is_faved = false
9 | _.each(favourites, function(n) {
10 | if (n.id == note.id) {
11 | is_faved = true
12 | }
13 | })
14 | cb(is_faved)
15 | })
16 | },
17 | star: function(note, cb) {
18 | this.fetchAll(function(favourites) {
19 | favourites.push(note)
20 | var item = {}
21 | item[STORAGE_STAR] = favourites
22 | storage.set(item, function() {
23 | cb({ is_starred: true })
24 | })
25 | })
26 | },
27 | unstar: function(note, cb) {
28 | this.fetchAll(function(favourites) {
29 | favourites = favourites.filter(function(n) {
30 | return n.id != note.id
31 | })
32 | var item = {}
33 | item[STORAGE_STAR] = favourites
34 | storage.set(item, function() {
35 | cb({ is_starred: false })
36 | })
37 | })
38 | },
39 | fetchAll: function(callback) {
40 | storage.get( STORAGE_STAR , function(resp) {
41 | if (resp && resp[STORAGE_STAR]) {
42 | callback(resp[STORAGE_STAR])
43 | } else {
44 | callback([])
45 | }
46 | })
47 | }
48 | }
49 | }])
--------------------------------------------------------------------------------
/assets/js/services/filesystemService.js:
--------------------------------------------------------------------------------
1 | // for packaged app
2 | app.factory('FileSystemService', [function() {
3 | function errorHandler(e) {
4 | console.error(e);
5 | }
6 | function waitForIO(writer, callback) {
7 | // set a watchdog to avoid eventual locking:
8 | var start = Date.now();
9 | // wait for a few seconds
10 | var reentrant = function() {
11 | if (writer.readyState===writer.WRITING && Date.now()-start<4000) {
12 | setTimeout(reentrant, 100);
13 | return;
14 | }
15 | if (writer.readyState===writer.WRITING) {
16 | console.error("Write operation taking too long, aborting!"+
17 | " (current writer readyState is "+writer.readyState+")");
18 | writer.abort();
19 | }
20 | else {
21 | callback();
22 | }
23 | };
24 | setTimeout(reentrant, 100);
25 | }
26 | function writeEntry(writableEntry, opt_blob, callback) {
27 |
28 | writableEntry.createWriter(function(writer) {
29 |
30 | writer.onerror = errorHandler;
31 | writer.onwriteend = callback;
32 |
33 | // If we have data, write it to the file. Otherwise, just use the file we
34 | // loaded.
35 | if (opt_blob) {
36 | writer.truncate(opt_blob.size);
37 | waitForIO(writer, function() {
38 | writer.seek(0);
39 | writer.write(opt_blob);
40 | });
41 | }
42 | }, errorHandler);
43 | }
44 | return {
45 | 'save': function(blob, filename, extension, callback) {
46 | chrome.fileSystem.chooseEntry({
47 | type: 'saveFile',
48 | suggestedName: filename,
49 | accepts: [{
50 | extensions: [extension]
51 | }]
52 | }, function(writableEntry) {
53 | if (!writableEntry) {
54 | callback(false)
55 | return
56 | }
57 | writeEntry(writableEntry, blob, function() {
58 | callback(true)
59 | })
60 | })
61 | }
62 | }
63 | }])
--------------------------------------------------------------------------------
/assets/js/services/friendsService.js:
--------------------------------------------------------------------------------
1 | app.factory('FriendsService', ['$q', '$http', '$rootScope', function($q, $http, $rootScope) {
2 |
3 | var localData
4 | , start = 0
5 | , count = 50
6 | , has_more = true
7 |
8 | function getAllLocal() {
9 | if (!localData) {
10 | var id = $rootScope.user.id
11 | localData = sessionStorage.getItem(id + '_friends')
12 | localData = JSON.parse(localData)
13 | localData = localData || []
14 | }
15 | return localData
16 | }
17 |
18 | function fetchFriendsList(start, count) {
19 | var defer = $q.defer()
20 | , user = $rootScope.user
21 | $http({
22 | method: 'GET',
23 | url: '/api/shuo/v2/users/' + user.uid + '/following',
24 | params: {
25 | start: start,
26 | count: count
27 | },
28 | cache: true
29 | })
30 | .success(function(resp) {
31 | Array.prototype.push.apply(localData, _.filter(resp, function(u) { return u.type == 'user' }))
32 | if (!resp.length) {
33 | has_more = false
34 | }
35 | defer.resolve(localData)
36 | })
37 | .error(defer.reject)
38 |
39 | return defer.promise
40 | }
41 |
42 | return {
43 | friends: localData,
44 | has_more: has_more,
45 | getAllLocal: getAllLocal,
46 | getMore: function() {
47 | var promise = fetchFriendsList(start, count)
48 | start += count
49 | return promise
50 | }
51 | }
52 | }])
--------------------------------------------------------------------------------
/assets/js/services/httpLoadingIntercepter.js:
--------------------------------------------------------------------------------
1 | app.factory('HttpLoadingIntercepter', ['$q', '$rootScope', function($q, $rootScope) {
2 | var n_loading = 0
3 | function isSerious(rejection) {
4 | if (rejection.data.type === 'text/html') {
5 | return false
6 | }
7 | return true
8 | }
9 |
10 | return {
11 | 'request': function(config) {
12 | n_loading++
13 | $rootScope.$broadcast('loading:show')
14 | return config || $q.when(config)
15 | },
16 | 'requestError': function(rejection) {
17 | n_loading--
18 | return $q.reject(rejection)
19 | },
20 | 'response': function(response) {
21 | if (!(--n_loading)) {
22 | $rootScope.$broadcast('loading:hide')
23 | }
24 | return response || $q.when(response)
25 | },
26 | 'responseError': function(rejection) {
27 | if (isSerious(rejection)) {
28 | $rootScope.$broadcast('alert:error', rejection.data)
29 | }
30 | if (!(--n_loading)) {
31 | $rootScope.$broadcast('loading:hide')
32 | }
33 | return $q.reject(rejection)
34 | }
35 | }
36 | }])
--------------------------------------------------------------------------------
/assets/js/services/httpOAuthIntercepter.js:
--------------------------------------------------------------------------------
1 | app.factory('HttpOAuthIntercepter', ['$q', '$rootScope', function($q, $rootScope) {
2 | return {
3 | request: function(req) {
4 | if (req.url.indexOf('/api') == 0) { // api.douban.com
5 | req.headers['Authorization'] = 'Bearer ' + $rootScope.user.token
6 | req.url = 'https://api.douban.com' + req.url.slice(4)
7 | }
8 | return req
9 | }
10 | }
11 | }])
--------------------------------------------------------------------------------
/assets/js/services/noteService.js:
--------------------------------------------------------------------------------
1 | app.factory('NoteService', ['$http', '$rootScope', 'UserService', 'BookService', function($http, $rootScope, UserService, BookService) {
2 | var user = $rootScope.user
3 | , id = user.id
4 | , STORAGE_BOOK = id + '_books'
5 | , STORAGE_NOTE = id + '_notes'
6 | var buildFormData = function(payload, files) {
7 | var formData = new FormData()
8 | _.each(payload, function(val, key) {
9 | formData.append(key, val)
10 | })
11 | _.each(files, function(file) {
12 | formData.append(file.name, file.obj)
13 | })
14 | return formData
15 | }
16 |
17 | function increaseNoteCount(bid, delta) {
18 | delta = delta || 1
19 | var books = JSON.parse(sessionStorage.getItem(STORAGE_BOOK))
20 | , book = books[bid]
21 | if (book) {
22 | book.notes_count += delta
23 | sessionStorage.setItem(STORAGE_BOOK, JSON.stringify(books))
24 | } else { // add a new book
25 | BookService.get(bid).then(function(book) {
26 | book.notes_count = 1
27 | books[bid] = book
28 | sessionStorage.setItem(STORAGE_BOOK, JSON.stringify(books))
29 | })
30 | }
31 | }
32 | return {
33 | get: function(params) {
34 | return $http.get('/api/v2/book/annotation/' + params.id)
35 | },
36 | put: function(params, payload, photos) {
37 | var formData = buildFormData(payload, photos)
38 | return $http.put('/api/v2/book/annotation/' + params.id, formData, {
39 | transformRequest: angular.identity,
40 | headers: {'Content-Type': undefined}
41 | }).success(function(note) {
42 | var notes = sessionStorage.getItem(STORAGE_NOTE)
43 | if (notes) {
44 | notes = JSON.parse(notes)
45 | notes.forEach(function(n, i) {
46 | if (n.id == note.id) {
47 | notes[i] = note
48 | }
49 | })
50 | sessionStorage.setItem(STORAGE_NOTE, JSON.stringify(notes))
51 | }
52 | })
53 | },
54 | post: function(params, payload, photos) {
55 | var formData = buildFormData(payload, photos)
56 | return $http.post('/api/v2/book/' + params.bid + '/annotations', formData, {
57 | transformRequest: angular.identity,
58 | headers: {'Content-Type': undefined}
59 | }).success(function(note) {
60 | var notes = sessionStorage.getItem(STORAGE_NOTE)
61 | if (notes) {
62 | notes = JSON.parse(notes)
63 | notes.push(note)
64 | sessionStorage.setItem(STORAGE_NOTE, JSON.stringify(notes))
65 |
66 | increaseNoteCount(note.book_id)
67 | }
68 | })
69 | },
70 | remove: function(params) {
71 | var id = params.id
72 | , bid = params.bid
73 | return $http.delete('/api/v2/book/annotation/' + id).success(function() {
74 | var notes = sessionStorage.getItem(STORAGE_NOTE)
75 | if (notes) {
76 | notes = JSON.parse(notes)
77 | notes = notes.filter(function(note) {
78 | return note.id != id
79 | })
80 | sessionStorage.setItem(STORAGE_NOTE, JSON.stringify(notes))
81 |
82 | increaseNoteCount(bid, -1)
83 | }
84 | })
85 | }
86 | }
87 | }])
--------------------------------------------------------------------------------
/assets/js/services/serializeService.js:
--------------------------------------------------------------------------------
1 | app.factory('SerializeService', function($q, $http, $rootScope, BookService) {
2 | function sync(url, start, count, callback) {
3 | sync.books = sync.books || {}
4 | sync.notes = sync.notes || []
5 | $http({
6 | method: 'GET',
7 | url: url,
8 | params: {
9 | start: start,
10 | count: count
11 | }
12 | }).success(function(resp) {
13 |
14 | var annotations = resp.annotations
15 | , books = sync.books
16 | , notes = sync.notes
17 | , book
18 | annotations.forEach(function(note) {
19 | notes.push(_.pick(note, 'id', 'book_id', 'chapter', 'time', 'summary', 'content', 'privacy', 'page_no', 'last_photo', 'photos'))
20 | book = _.pick(note.book, 'id', 'title', 'author', 'images', 'summary')
21 | if (!books[book.id]) {
22 | books[book.id] = _.extend(book, {
23 | notes_count: 1
24 | })
25 | } else {
26 | books[book.id].notes_count += 1
27 | }
28 | })
29 | if (annotations.length) sync(url, start+count, count, callback)
30 | else callback()
31 | })
32 | }
33 | return {
34 | serialize: function(id, callback) {
35 | sync.books = {}
36 | sync.notes = []
37 | sync('/api/v2/book/user/' + id + '/annotations', 0, 20, function() {
38 | sessionStorage.setItem(id+ '_books', JSON.stringify(sync.books))
39 | sessionStorage.setItem(id+ '_notes', JSON.stringify(sync.notes))
40 | callback(sync.books, sync.notes)
41 | })
42 | },
43 | fetchAllBooks: function(id) {
44 | var defer = new $q.defer()
45 | , books = sessionStorage.getItem(id+ '_books')
46 | if (books) {
47 | books = JSON.parse(books)
48 | defer.resolve(books)
49 | } else {
50 | this.serialize(id, function(books) {
51 | defer.resolve(books)
52 | })
53 | }
54 | return defer.promise
55 | },
56 | fetchBook: function(uid, bid) {
57 | var defer = new $q.defer()
58 | , books = sessionStorage.getItem(uid+ '_books')
59 | , book
60 | , notes
61 | if (books) {
62 | books = JSON.parse(books)
63 | book = books[bid]
64 | }
65 | if (book) { // book exist in sessionStorage
66 | notes = JSON.parse(sessionStorage.getItem(uid+ '_notes'))
67 | notes = notes.filter(function(note) {
68 | return note.book_id == bid
69 | })
70 | book.notes = notes
71 | defer.resolve(book)
72 | } else {
73 | this.serialize(uid, function(books, notes) {
74 | book = books[bid]
75 | if (book) { // book exist in user's library
76 | book.notes = notes.filter(function(note) {
77 | return note.book_id == book.id
78 | })
79 | defer.resolve(book)
80 | } else { // new book, never write notes for it
81 | BookService.get(bid).then(function(book) {
82 | book.notes_count = 0
83 | book.notes = []
84 | defer.resolve(book)
85 | })
86 | }
87 | })
88 | }
89 | return defer.promise
90 | },
91 | fetchAll: function(uid) {
92 | var defer = new $q.defer()
93 | , books = sessionStorage.getItem(uid+ '_books')
94 | , notes = sessionStorage.getItem(uid+ '_notes')
95 | if (books && notes) {
96 | books = JSON.parse(books)
97 | notes = JSON.parse(notes)
98 | defer.resolve([books, notes])
99 | } else {
100 | this.serialize(uid, function(books, notes) {
101 | defer.resolve([books, notes])
102 | })
103 | }
104 | return defer.promise
105 | }
106 | }
107 |
108 | })
--------------------------------------------------------------------------------
/assets/js/services/translateService.js:
--------------------------------------------------------------------------------
1 | app.factory('TranslateService', function() {
2 | var g_html_blocks = []
3 | var escapeCharacters = function(text, charsToEscape, afterBackslash) {
4 | var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])"
5 | if (afterBackslash) {
6 | regexString = "\\\\" + regexString
7 | }
8 | var regex = new RegExp(regexString,"g")
9 | text = text.replace(regex,escapeCharacters_callback)
10 | return text
11 | }
12 | var escapeCharacters_callback = function(wholeMatch,m1) {
13 | var charCodeToEscape = m1.charCodeAt(0)
14 | return "~E"+charCodeToEscape+"E"
15 | }
16 | var _EncodeCode = function(text) {
17 | text = text.replace(/&/g,"&")
18 | text = text.replace(//g,">")
20 | text = escapeCharacters(text,"\*_{}[]\\",false)
21 | return text
22 | }
23 | var _Detab = function(text) {
24 | text = text.replace(/\t(?=\t)/g," ")
25 | text = text.replace(/\t/g,"~A~B")
26 | text = text.replace(/~B(.+?)~A/g, function(wholeMatch,m1,m2) {
27 | var leadingText = m1
28 | var numSpaces = 4 - leadingText.length % 4
29 | for (var i=0; i/g, function(_, num) {
47 | return ''
48 | })
49 | }
50 |
51 | return text
52 | .replace(/\<代码开始 lang=\"(.*)\"\>(\n{0,1})/g, '```$1\n')
53 | .replace(/\<\/代码结束\>/g, '```')
54 | .replace(/\[code:(.*)\]/g, '```$1')
55 | .replace(/\[\/code\]/g, '```')
56 | .replace(/\<原文开始\>([\s\S]*?)\<\/原文结束\>/g, function(wholeMatch, m1) {
57 | var bq = m1
58 | // remove leading and trailing newlines
59 | bq = bq.replace(/^\n/g, "")
60 | //bq = bq.replace(/\n$/g, "")
61 | bq = bq.replace(/^([^\n|^\s])/gm, '> $1')
62 | return bq
63 | })
64 | },
65 | markdownToDouban: function(text) {
66 | text += '\n'
67 | return text.replace(/(^|\n)```(.*)\n([\s\S]*?)\n```/g, function(wholeMatch, m0, m1, m2) {
68 | var blank = m0
69 | , language = m1
70 | , codeblock = m2
71 |
72 | // codeblock = _EncodeCode(codeblock)
73 | codeblock = _Detab(codeblock)
74 | codeblock = codeblock.replace(/^\n+/g,"") // trim leading newlines
75 | codeblock = codeblock.replace(/\n+$/g,"") // trim trailing whitespace
76 |
77 | codeblock = blank + "<代码开始" + (language ? " lang=\"" + language + '"' : "") + ">\n" + codeblock + "\n代码结束>"
78 |
79 | return codeblock
80 | }).replace(/\!\[图片(\d*)\]\((.*)\)/g, '<图片$1>')
81 | .replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, function(wholeMatch, m1) {
82 | var bq = m1
83 | bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0")
84 | bq = bq.replace(/~0/g, "")
85 | bq = bq.replace(/^[ \t]+$/gm,"")
86 | bq = bq.replace(/\n{1,}$/, '')
87 |
88 | return "<原文开始>\n" + bq + "\n原文结束>\n"
89 | }).replace(/\n$/g, '')
90 | }
91 | }
92 | })
--------------------------------------------------------------------------------
/assets/js/services/userService.js:
--------------------------------------------------------------------------------
1 | app.factory('UserService', function($rootScope, $http, $q) {
2 | return {
3 | isLoggedIn: function() {
4 | var defer = new $q.defer()
5 | chrome.storage.local.get('logintoken', function(resp) {
6 | if ((!_.isEmpty(resp)) && resp.logintoken) {
7 | defer.resolve()
8 | } else {
9 | defer.reject()
10 | }
11 | })
12 | return defer.promise
13 | },
14 | // isLoggedIn: !!chrome.storage.local.get('logintoken'),
15 | getUser: function(uid) {
16 | var defer = new $q.defer()
17 | , self = this
18 | this.login().then(function(user) {
19 | uid = uid || user.uid
20 | if (user.uid == uid) {
21 | user.is_self = true
22 | defer.resolve(user)
23 | } else {
24 | self.getUserInfo(uid).then(function(user) {
25 | user.is_self = false
26 | defer.resolve(_.pick(user, 'id', 'uid', 'name'))
27 | })
28 | }
29 | }, function() {
30 | defer.reject()
31 | })
32 | return defer.promise
33 | },
34 | getUserInfo: function(uid) {
35 | var defer = new $q.defer()
36 |
37 | this.login().then(function(user) {
38 | uid = uid || user.uid
39 | $http({
40 | method: 'GET',
41 | url: '/api/v2/user/' + uid,
42 | cache: true
43 | }).success(function(userinfo) {
44 | defer.resolve(userinfo)
45 | })
46 | }, function() {
47 | defer.reject()
48 | })
49 | return defer.promise
50 | },
51 | login: function() {
52 | var defer = new $q.defer()
53 | chrome.storage.local.get('logintoken', function(resp) {
54 | if (resp.logintoken) {
55 | $rootScope.user = resp.logintoken
56 | defer.resolve($rootScope.user)
57 | } else {
58 | defer.reject()
59 | }
60 | })
61 | return defer.promise
62 | },
63 | logout: function(callback) {
64 | chrome.storage.local.remove('logintoken', function() {
65 | $rootScope.user = null
66 | callback()
67 | })
68 | }
69 | }
70 | })
--------------------------------------------------------------------------------
/assets/js/ui-directives.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | angular.module('ANNO.ui-directives', [])
4 | .directive('scrollHide', [function() {
5 | return function(scope, elem, attrs) {
6 | var hideFrom = attrs.hideFrom || 10
7 | , target = window
8 |
9 | if (attrs.targetSel) {
10 | target = document.querySelector(attrs.targetSel)
11 | }
12 |
13 | if (attrs.hideFrom) {
14 | scope.$watch(attrs.hideFrom, function(value) {
15 | hideFrom = parseInt(value, 10)
16 | })
17 | }
18 |
19 | function onScroll() {
20 | elem.toggleClass('hide', target.scrollTop > hideFrom)
21 | }
22 | scope.hide = false
23 |
24 | angular.element(target).on('scroll', _.throttle(onScroll, 200))
25 | }
26 | }])
27 |
--------------------------------------------------------------------------------
/assets/lib/angular/angular-cookies.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license AngularJS v1.2.13
3 | * (c) 2010-2014 Google, Inc. http://angularjs.org
4 | * License: MIT
5 | */
6 | (function(window, angular, undefined) {'use strict';
7 |
8 | /**
9 | * @ngdoc overview
10 | * @name ngCookies
11 | * @description
12 | *
13 | * # ngCookies
14 | *
15 | * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
16 | *
17 | * {@installModule cookies}
18 | *
19 | *
20 | *
21 | * See {@link ngCookies.$cookies `$cookies`} and
22 | * {@link ngCookies.$cookieStore `$cookieStore`} for usage.
23 | */
24 |
25 |
26 | angular.module('ngCookies', ['ng']).
27 | /**
28 | * @ngdoc object
29 | * @name ngCookies.$cookies
30 | * @requires $browser
31 | *
32 | * @description
33 | * Provides read/write access to browser's cookies.
34 | *
35 | * Only a simple Object is exposed and by adding or removing properties to/from
36 | * this object, new cookies are created/deleted at the end of current $eval.
37 | *
38 | * Requires the {@link ngCookies `ngCookies`} module to be installed.
39 | *
40 | * @example
41 |
42 |
43 |
51 |
52 |
53 | */
54 | factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
55 | var cookies = {},
56 | lastCookies = {},
57 | lastBrowserCookies,
58 | runEval = false,
59 | copy = angular.copy,
60 | isUndefined = angular.isUndefined;
61 |
62 | //creates a poller fn that copies all cookies from the $browser to service & inits the service
63 | $browser.addPollFn(function() {
64 | var currentCookies = $browser.cookies();
65 | if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
66 | lastBrowserCookies = currentCookies;
67 | copy(currentCookies, lastCookies);
68 | copy(currentCookies, cookies);
69 | if (runEval) $rootScope.$apply();
70 | }
71 | })();
72 |
73 | runEval = true;
74 |
75 | //at the end of each eval, push cookies
76 | //TODO: this should happen before the "delayed" watches fire, because if some cookies are not
77 | // strings or browser refuses to store some cookies, we update the model in the push fn.
78 | $rootScope.$watch(push);
79 |
80 | return cookies;
81 |
82 |
83 | /**
84 | * Pushes all the cookies from the service to the browser and verifies if all cookies were
85 | * stored.
86 | */
87 | function push() {
88 | var name,
89 | value,
90 | browserCookies,
91 | updated;
92 |
93 | //delete any cookies deleted in $cookies
94 | for (name in lastCookies) {
95 | if (isUndefined(cookies[name])) {
96 | $browser.cookies(name, undefined);
97 | }
98 | }
99 |
100 | //update all cookies updated in $cookies
101 | for(name in cookies) {
102 | value = cookies[name];
103 | if (!angular.isString(value)) {
104 | if (angular.isDefined(lastCookies[name])) {
105 | cookies[name] = lastCookies[name];
106 | } else {
107 | delete cookies[name];
108 | }
109 | } else if (value !== lastCookies[name]) {
110 | $browser.cookies(name, value);
111 | updated = true;
112 | }
113 | }
114 |
115 | //verify what was actually stored
116 | if (updated){
117 | updated = false;
118 | browserCookies = $browser.cookies();
119 |
120 | for (name in cookies) {
121 | if (cookies[name] !== browserCookies[name]) {
122 | //delete or reset all cookies that the browser dropped from $cookies
123 | if (isUndefined(browserCookies[name])) {
124 | delete cookies[name];
125 | } else {
126 | cookies[name] = browserCookies[name];
127 | }
128 | updated = true;
129 | }
130 | }
131 | }
132 | }
133 | }]).
134 |
135 |
136 | /**
137 | * @ngdoc object
138 | * @name ngCookies.$cookieStore
139 | * @requires $cookies
140 | *
141 | * @description
142 | * Provides a key-value (string-object) storage, that is backed by session cookies.
143 | * Objects put or retrieved from this storage are automatically serialized or
144 | * deserialized by angular's toJson/fromJson.
145 | *
146 | * Requires the {@link ngCookies `ngCookies`} module to be installed.
147 | *
148 | * @example
149 | */
150 | factory('$cookieStore', ['$cookies', function($cookies) {
151 |
152 | return {
153 | /**
154 | * @ngdoc method
155 | * @name ngCookies.$cookieStore#get
156 | * @methodOf ngCookies.$cookieStore
157 | *
158 | * @description
159 | * Returns the value of given cookie key
160 | *
161 | * @param {string} key Id to use for lookup.
162 | * @returns {Object} Deserialized cookie value.
163 | */
164 | get: function(key) {
165 | var value = $cookies[key];
166 | return value ? angular.fromJson(value) : value;
167 | },
168 |
169 | /**
170 | * @ngdoc method
171 | * @name ngCookies.$cookieStore#put
172 | * @methodOf ngCookies.$cookieStore
173 | *
174 | * @description
175 | * Sets a value for given cookie key
176 | *
177 | * @param {string} key Id for the `value`.
178 | * @param {Object} value Value to be stored.
179 | */
180 | put: function(key, value) {
181 | $cookies[key] = angular.toJson(value);
182 | },
183 |
184 | /**
185 | * @ngdoc method
186 | * @name ngCookies.$cookieStore#remove
187 | * @methodOf ngCookies.$cookieStore
188 | *
189 | * @description
190 | * Remove given cookie
191 | *
192 | * @param {string} key Id of the key-value pair to delete.
193 | */
194 | remove: function(key) {
195 | delete $cookies[key];
196 | }
197 | };
198 |
199 | }]);
200 |
201 |
202 | })(window, window.angular);
203 |
--------------------------------------------------------------------------------
/assets/lib/ng-infinite-scroll/ng-infinite-scroll.js:
--------------------------------------------------------------------------------
1 | /* ng-infinite-scroll - v1.0.0 - 2013-02-23 */
2 | var mod;
3 |
4 | mod = angular.module('infinite-scroll', []);
5 |
6 | mod.directive('infiniteScroll', [
7 | '$rootScope', '$timeout', function($rootScope, $timeout) {
8 | return {
9 | link: function(scope, elem, attrs) {
10 | var scrollElemSel = attrs.scrollElemSel || 'body'
11 | , scrollElem = document.querySelector(scrollElemSel)
12 | , $scrollElem = angular.element(scrollElem)
13 |
14 | var checkWhenEnabled, handler, scrollDistance, scrollEnabled;
15 | scrollDistance = 0;
16 | if (attrs.infiniteScrollDistance != null) {
17 | scope.$watch(attrs.infiniteScrollDistance, function(value) {
18 | return scrollDistance = parseInt(value, 10);
19 | });
20 | }
21 | scrollEnabled = true;
22 | checkWhenEnabled = false;
23 | if (attrs.infiniteScrollDisabled != null) {
24 | scope.$watch(attrs.infiniteScrollDisabled, function(value) {
25 | scrollEnabled = !value;
26 | if (scrollEnabled && checkWhenEnabled) {
27 | checkWhenEnabled = false;
28 | return handler();
29 | }
30 | });
31 | }
32 | handler = function() {
33 | var elementBottom, remaining, shouldScroll, windowBottom;
34 | windowBottom = scrollElem.clientHeight + scrollElem.scrollTop;
35 | elementBottom = elem[0].offsetTop + elem[0].clientHeight;
36 | remaining = elementBottom - windowBottom;
37 | shouldScroll = remaining <= scrollElem.clientHeight * scrollDistance;
38 | if (shouldScroll && scrollEnabled) {
39 | if ($rootScope.$$phase) {
40 | return scope.$eval(attrs.infiniteScroll);
41 | } else {
42 | return scope.$apply(attrs.infiniteScroll);
43 | }
44 | } else if (shouldScroll) {
45 | return checkWhenEnabled = true;
46 | }
47 | };
48 | $scrollElem.on('scroll', _.throttle(handler, 200));
49 | scope.$on('$destroy', function() {
50 | return $scrollElem.off('scroll', handler);
51 | });
52 | return $timeout((function() {
53 | if (attrs.infiniteScrollImmediateCheck) {
54 | if (scope.$eval(attrs.infiniteScrollImmediateCheck)) {
55 | return handler();
56 | }
57 | } else {
58 | return handler();
59 | }
60 | }), 0);
61 | }
62 | };
63 | }
64 | ]);
65 |
--------------------------------------------------------------------------------
/assets/resource/background.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html', {
3 | 'minWidth': 860,
4 | 'minHeight': 580,
5 | 'bounds': {
6 | 'width': 960,
7 | 'height': 600
8 | }
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/assets/resource/css/themes/solarized_dark.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | padding: 0.5em;
10 | background: #002b36;
11 | color: #839496;
12 | }
13 |
14 | .hljs-comment,
15 | .hljs-template_comment,
16 | .diff .hljs-header,
17 | .hljs-doctype,
18 | .hljs-pi,
19 | .lisp .hljs-string,
20 | .hljs-javadoc {
21 | color: #586e75;
22 | }
23 |
24 | /* Solarized Green */
25 | .hljs-keyword,
26 | .hljs-winutils,
27 | .method,
28 | .hljs-addition,
29 | .css .hljs-tag,
30 | .hljs-request,
31 | .hljs-status,
32 | .nginx .hljs-title {
33 | color: #859900;
34 | }
35 |
36 | /* Solarized Cyan */
37 | .hljs-number,
38 | .hljs-command,
39 | .hljs-string,
40 | .hljs-tag .hljs-value,
41 | .hljs-rules .hljs-value,
42 | .hljs-phpdoc,
43 | .tex .hljs-formula,
44 | .hljs-regexp,
45 | .hljs-hexcolor,
46 | .hljs-link_url {
47 | color: #2aa198;
48 | }
49 |
50 | /* Solarized Blue */
51 | .hljs-title,
52 | .hljs-localvars,
53 | .hljs-chunk,
54 | .hljs-decorator,
55 | .hljs-built_in,
56 | .hljs-identifier,
57 | .vhdl .hljs-literal,
58 | .hljs-id,
59 | .css .hljs-function {
60 | color: #268bd2;
61 | }
62 |
63 | /* Solarized Yellow */
64 | .hljs-attribute,
65 | .hljs-variable,
66 | .lisp .hljs-body,
67 | .smalltalk .hljs-number,
68 | .hljs-constant,
69 | .hljs-class .hljs-title,
70 | .hljs-parent,
71 | .haskell .hljs-type,
72 | .hljs-link_reference {
73 | color: #b58900;
74 | }
75 |
76 | /* Solarized Orange */
77 | .hljs-preprocessor,
78 | .hljs-preprocessor .hljs-keyword,
79 | .hljs-pragma,
80 | .hljs-shebang,
81 | .hljs-symbol,
82 | .hljs-symbol .hljs-string,
83 | .diff .hljs-change,
84 | .hljs-special,
85 | .hljs-attr_selector,
86 | .hljs-subst,
87 | .hljs-cdata,
88 | .clojure .hljs-title,
89 | .css .hljs-pseudo,
90 | .hljs-header {
91 | color: #cb4b16;
92 | }
93 |
94 | /* Solarized Red */
95 | .hljs-deletion,
96 | .hljs-important {
97 | color: #dc322f;
98 | }
99 |
100 | /* Solarized Violet */
101 | .hljs-link_label {
102 | color: #6c71c4;
103 | }
104 |
105 | .tex .hljs-formula {
106 | background: #073642;
107 | }
108 |
--------------------------------------------------------------------------------
/assets/resource/css/themes/tomorrow.css:
--------------------------------------------------------------------------------
1 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
2 |
3 | /* Tomorrow Comment */
4 | .hljs-comment,
5 | .hljs-title {
6 | color: #8e908c;
7 | }
8 |
9 | /* Tomorrow Red */
10 | .hljs-variable,
11 | .hljs-attribute,
12 | .hljs-tag,
13 | .hljs-regexp,
14 | .ruby .hljs-constant,
15 | .xml .hljs-tag .hljs-title,
16 | .xml .hljs-pi,
17 | .xml .hljs-doctype,
18 | .html .hljs-doctype,
19 | .css .hljs-id,
20 | .css .hljs-class,
21 | .css .hljs-pseudo {
22 | color: #c82829;
23 | }
24 |
25 | /* Tomorrow Orange */
26 | .hljs-number,
27 | .hljs-preprocessor,
28 | .hljs-pragma,
29 | .hljs-built_in,
30 | .hljs-literal,
31 | .hljs-params,
32 | .hljs-constant {
33 | color: #f5871f;
34 | }
35 |
36 | /* Tomorrow Yellow */
37 | .ruby .hljs-class .hljs-title,
38 | .css .hljs-rules .hljs-attribute {
39 | color: #eab700;
40 | }
41 |
42 | /* Tomorrow Green */
43 | .hljs-string,
44 | .hljs-value,
45 | .hljs-inheritance,
46 | .hljs-header,
47 | .ruby .hljs-symbol,
48 | .xml .hljs-cdata {
49 | color: #718c00;
50 | }
51 |
52 | /* Tomorrow Aqua */
53 | .css .hljs-hexcolor {
54 | color: #3e999f;
55 | }
56 |
57 | /* Tomorrow Blue */
58 | .hljs-function,
59 | .python .hljs-decorator,
60 | .python .hljs-title,
61 | .ruby .hljs-function .hljs-title,
62 | .ruby .hljs-title .hljs-keyword,
63 | .perl .hljs-sub,
64 | .javascript .hljs-title,
65 | .coffeescript .hljs-title {
66 | color: #4271ae;
67 | }
68 |
69 | /* Tomorrow Purple */
70 | .hljs-keyword,
71 | .javascript .hljs-function {
72 | color: #8959a8;
73 | }
74 |
75 | .hljs {
76 | display: block;
77 | background: white;
78 | color: #4d4d4c;
79 | padding: 0.5em;
80 | }
81 |
82 | .coffeescript .javascript,
83 | .javascript .xml,
84 | .tex .hljs-formula,
85 | .xml .javascript,
86 | .xml .vbscript,
87 | .xml .css,
88 | .xml .hljs-cdata {
89 | opacity: 0.5;
90 | }
91 |
--------------------------------------------------------------------------------
/assets/resource/css/themes/zenburn.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov
4 | based on dark.css by Ivan Sagalaev
5 |
6 | */
7 |
8 | .hljs {
9 | display: block; padding: 0.5em;
10 | background: #3F3F3F;
11 | color: #DCDCDC;
12 | }
13 |
14 | .hljs-keyword,
15 | .hljs-tag,
16 | .css .hljs-class,
17 | .css .hljs-id,
18 | .lisp .hljs-title,
19 | .nginx .hljs-title,
20 | .hljs-request,
21 | .hljs-status,
22 | .clojure .hljs-attribute {
23 | color: #E3CEAB;
24 | }
25 |
26 | .django .hljs-template_tag,
27 | .django .hljs-variable,
28 | .django .hljs-filter .hljs-argument {
29 | color: #DCDCDC;
30 | }
31 |
32 | .hljs-number,
33 | .hljs-date {
34 | color: #8CD0D3;
35 | }
36 |
37 | .dos .hljs-envvar,
38 | .dos .hljs-stream,
39 | .hljs-variable,
40 | .apache .hljs-sqbracket {
41 | color: #EFDCBC;
42 | }
43 |
44 | .dos .hljs-flow,
45 | .diff .hljs-change,
46 | .python .exception,
47 | .python .hljs-built_in,
48 | .hljs-literal,
49 | .tex .hljs-special {
50 | color: #EFEFAF;
51 | }
52 |
53 | .diff .hljs-chunk,
54 | .hljs-subst {
55 | color: #8F8F8F;
56 | }
57 |
58 | .dos .hljs-keyword,
59 | .python .hljs-decorator,
60 | .hljs-title,
61 | .haskell .hljs-type,
62 | .diff .hljs-header,
63 | .ruby .hljs-class .hljs-parent,
64 | .apache .hljs-tag,
65 | .nginx .hljs-built_in,
66 | .tex .hljs-command,
67 | .hljs-prompt {
68 | color: #efef8f;
69 | }
70 |
71 | .dos .hljs-winutils,
72 | .ruby .hljs-symbol,
73 | .ruby .hljs-symbol .hljs-string,
74 | .ruby .hljs-string {
75 | color: #DCA3A3;
76 | }
77 |
78 | .diff .hljs-deletion,
79 | .hljs-string,
80 | .hljs-tag .hljs-value,
81 | .hljs-preprocessor,
82 | .hljs-pragma,
83 | .hljs-built_in,
84 | .sql .hljs-aggregate,
85 | .hljs-javadoc,
86 | .smalltalk .hljs-class,
87 | .smalltalk .hljs-localvars,
88 | .smalltalk .hljs-array,
89 | .css .hljs-rules .hljs-value,
90 | .hljs-attr_selector,
91 | .hljs-pseudo,
92 | .apache .hljs-cbracket,
93 | .tex .hljs-formula,
94 | .coffeescript .hljs-attribute {
95 | color: #CC9393;
96 | }
97 |
98 | .hljs-shebang,
99 | .diff .hljs-addition,
100 | .hljs-comment,
101 | .java .hljs-annotation,
102 | .hljs-template_comment,
103 | .hljs-pi,
104 | .hljs-doctype {
105 | color: #7F9F7F;
106 | }
107 |
108 | .coffeescript .javascript,
109 | .javascript .xml,
110 | .tex .hljs-formula,
111 | .xml .javascript,
112 | .xml .vbscript,
113 | .xml .css,
114 | .xml .hljs-cdata {
115 | opacity: 0.5;
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/assets/resource/fonts/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/assets/resource/fonts/icomoon.eot
--------------------------------------------------------------------------------
/assets/resource/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/assets/resource/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/assets/resource/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/assets/resource/fonts/icomoon.woff
--------------------------------------------------------------------------------
/assets/resource/img/books.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/assets/resource/img/books.png
--------------------------------------------------------------------------------
/assets/resource/img/login_with_douban_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/assets/resource/img/login_with_douban_32.png
--------------------------------------------------------------------------------
/assets/resource/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "读书笔记",
3 | "description": "豆瓣读书笔记第三方应用",
4 | "version": "0.2.6",
5 | "manifest_version": 2,
6 | "minimum_chrome_version": "29",
7 | "app": {
8 | "background": {
9 | "scripts": ["background.js"]
10 | }
11 | },
12 | "icons": { "16": "img/books.png", "128": "img/books.png" },
13 | "permissions": [
14 | {"fileSystem": ["write"]}
15 | , "storage"
16 | , "identity"
17 | , "https://www.douban.com/*", "https://api.douban.com/*", "http://*.douban.com/*"
18 | , "https://*.evernote.com/*", "http://*.evernote.com/*"
19 | , "https://*.yinxiang.com/*", "http://*.yinxiang.com/*"
20 | , "https://www.google-analytics.com/"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/assets/stylus/edit.styl:
--------------------------------------------------------------------------------
1 | @import 'lib/vendor'
2 |
3 | tip(font_size = 14px)
4 | [data-title]
5 | position relative
6 | &:after
7 | display none
8 | content attr(data-title) ""
9 | position absolute
10 | top 0
11 | margin-left -100%
12 | background rgba(0, 0, 0, .6)
13 | color #fff
14 | font-size font_size
15 | border-radius 3px
16 | padding (font_size/7) (font_size/2)
17 | line-height font_size * 1.65
18 | white-space pre
19 | //left 80%
20 | transform translate(-100%, 25%)
21 | &:hover
22 | &:after
23 | display inline-block
24 |
25 | modal()
26 | background rgba(0, 0, 0, .2)
27 | width 160px
28 | height 160px
29 | position fixed
30 | top 0
31 | left 0
32 | transform translate(-50%, 50%)
33 |
34 | html.global_editor
35 | height 100%
36 | overflow hidden
37 | body, .ng-view, .container
38 | height 100%
39 | body
40 | overflow hidden
41 | .ng-view
42 | overflow hidden
43 | width calc(100% \- 64px)
44 | margin-top 0
45 |
46 | #editor, #reader
47 | height 100%
48 | overflow-y scroll
49 |
50 | #reader
51 | overflow-x hidden
52 |
53 | #navigation
54 | position fixed
55 | z-index 100
56 | top 0
57 | right 0
58 | bottom 0
59 | padding 10px 12px
60 | margin 0
61 | text-align center
62 | // background #ddd
63 | background #eee
64 | li
65 | list-style none
66 | tip()
67 | a
68 | display inline-block
69 | font-size 1.5em
70 | width 40px
71 | height 40px
72 | line-height 40px
73 | border-radius 3px
74 | background #eee
75 | text-decoration none
76 | color #8d8d8d
77 | margin .3em 0
78 | &:hover
79 | background #e9e9e9
80 | box-shadow inset 0 0 3px #ccc
81 | &.active
82 | background #8d8d8d
83 | color #f1f1f1
84 | box-shadow 0 2px 0 #666
85 | &:hover
86 | box-shadow inset 0 0 3px rgba(0, 0, 0, .3), 0 2px 0 #666
87 |
88 | #editor, #reader
89 | .book-title
90 | font-size .8em
91 | color #ccc
92 |
93 | @import "widgets/editor.styl"
94 | @import "widgets/reader.styl"
95 |
96 | // three kinds of editing mode
97 | .column-mode
98 | #editor, #reader
99 | float left
100 | width 50%
101 | #editor
102 | margin 0
103 | form
104 | margin 40px 60px 0
105 | #reader
106 | box-sizing border-box
107 | padding 40px 40px 0
108 | border-left 1px solid #ccc
109 |
110 | .edit-mode
111 | #reader
112 | display none
113 | #editor
114 | form
115 | max-width 600px
116 | margin 40px auto 0
117 |
118 | .read-mode
119 | #editor
120 | display none
121 | #reader
122 | box-sizing border-box
123 | padding-top 40px
124 | pre
125 | background #eee
126 |
127 | .modal-help
128 | h3, h4
129 | margin-bottom 0
130 | h4
131 | margin-top 1em
132 | .modal-body
133 | line-height 1.65
134 | padding-top 0
--------------------------------------------------------------------------------
/assets/stylus/fonticon.styl:
--------------------------------------------------------------------------------
1 | @font-face
2 | font-family 'icomoon'
3 | src url('/fonts/icomoon.eot?-s5hseh')
4 | src url('/fonts/icomoon.eot?#iefix-s5hseh') format('embedded-opentype'),
5 | url('/fonts/icomoon.woff?-s5hseh') format('woff'),
6 | url('/fonts/icomoon.ttf?-s5hseh') format('truetype'),
7 | url('/fonts/icomoon.svg?-s5hseh#icomoon') format('svg')
8 | font-weight normal
9 | font-style normal
10 |
11 |
12 | [class^="icon-"], [class*=" icon-"]
13 | font-family 'icomoon'
14 | speak none
15 | font-style normal
16 | font-weight normal
17 | font-variant normal
18 | text-transform none
19 | line-height 1
20 |
21 | /* Better Font Rendering =========== */
22 | -webkit-font-smoothing antialiased
23 | -moz-osx-font-smoothing grayscale
24 |
25 |
26 | .icon-evernote:before
27 | content "\e600"
28 |
29 | .icon-lock:before
30 | content "\e602"
31 |
32 | .icon-feather:before
33 | content "\e603"
34 |
35 | .icon-leaf:before
36 | content "\e605"
37 |
38 | .icon-delete:before
39 | content "\e604"
40 |
41 | .icon-pen:before
42 | content "\e606"
43 |
44 | .icon-zoom-in:before
45 | content "\e60b"
46 |
47 | .icon-zoom-out:before
48 | content "\e60c"
49 |
50 | .icon-eye:before
51 | content "\e60a"
52 |
53 | .icon-save:before
54 | content "\e601"
55 |
56 | .icon-columns:before
57 | content "\e608"
58 |
59 | .icon-bold:before
60 | content "\e614"
61 |
62 | .icon-italic:before
63 | content "\e615"
64 |
65 | .icon-image:before
66 | content "\e616"
67 |
68 | .icon-left-quote:before
69 | content "\e617"
70 |
71 | .icon-info:before
72 | content "\e618"
73 |
74 | .icon-help:before
75 | content "\e61b"
76 |
77 | .icon-list:before
78 | content "\e619"
79 |
80 | .icon-numbered-list:before
81 | content "\e60f"
82 |
83 | .icon-link:before
84 | content "\e60d"
85 |
86 | .icon-sidebar:before
87 | content "\e61a"
88 |
89 | .icon-more:before
90 | content "\e613"
91 |
92 | .icon-fullscreen:before
93 | content "\e61d"
94 |
95 | .icon-restore:before
96 | content "\e61c"
97 |
98 | .icon-sad:before
99 | content "\e610"
100 |
101 | .icon-star:before
102 | content "\e607"
103 |
104 | .icon-star-empty:before
105 | content "\e61e"
106 |
107 | .icon-plus:before
108 | content "\e61f"
109 |
110 | .icon-export:before
111 | content "\e620"
112 |
113 | .icon-edit:before
114 | content "\e621"
115 | vertical-align middle
116 |
--------------------------------------------------------------------------------
/assets/stylus/lib/vendor.styl:
--------------------------------------------------------------------------------
1 | transform()
2 | -webkit-transform arguments
3 | -moz-transform arguments
4 | transform arguments
5 |
6 | transform-origin()
7 | -webkit-transform-origin arguments
8 | -moz-transform-origin arguments
9 | transform-origin arguments
10 |
11 | transform-style()
12 | -webkit-transform-style arguments
13 | -moz-transform-style arguments
14 | transform-style arguments
15 |
16 | perspective()
17 | -webkit-perspective arguments
18 | -moz-perspective arguments
19 | perspective arguments
20 |
21 | perspective-origin()
22 | -webkit-perspective-origin arguments
23 | -moz-perspective-origin arguments
24 | perspective-origin arguments
25 |
26 | transition()
27 | -webkit-transition arguments
28 | -moz-transition arguments
29 | transition arguments
30 |
31 | transition-transform()
32 | -webkit-transition -webkit-transform arguments
33 | -moz-transition -moz-transform arguments
34 | transition transform arguments
35 |
36 | backface-visibility()
37 | -webkit-backface-visibility arguments
38 | -moz-backface-visibility arguments
39 | backface-visibility arguments
40 |
--------------------------------------------------------------------------------
/assets/stylus/mod_about.styl:
--------------------------------------------------------------------------------
1 | #about
2 | line-height 1.65
3 | h1
4 | font-size 1.8em
5 | a
6 | color lightseagreen
7 | &:hover
8 | background lightseagreen
9 | color #fff
10 | h2 + ul, h2 ~ div
11 | padding-left 40px
12 | h2 ~ div
13 | line-height 2
14 | a
15 | display inline-block
16 | margin 0 .8em
17 | text-decoration none
--------------------------------------------------------------------------------
/assets/stylus/mod_aside.styl:
--------------------------------------------------------------------------------
1 | @import "lib/vendor"
2 |
3 | aside
4 | position fixed
5 | z-index 1000
6 | top 0
7 | left 0
8 | bottom 0
9 | transform translateX(-200px)
10 | transition-transform(.3s)
11 | width 200px
12 | background #363636
13 | padding-top 1em
14 | background-image: url(unquote('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoYRxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg=='))
15 | box-shadow inset -3px 0 8px #444
16 | &.show
17 | transform(translateX(0))
18 | ul
19 | margin 0
20 | padding 0
21 | li
22 | border-bottom 1px solid rgba(255,255,255,.1)
23 | a
24 | display block
25 | color #fefefe
26 | text-decoration none
27 | padding .8em 1.2em
28 | &:hover
29 | color lightseagreen
30 | .info
31 | position absolute
32 | left 0
33 | right 0
34 | bottom 0
35 | .banner
36 | height 120px
37 | .avatar
38 | display block
39 | width 100px
40 | height 100px
41 | border-radius 50px
42 | margin 0 auto
43 | background-size cover
44 | background-position center
45 | // .logo
46 | // // background rgba(32, 178, 170, .8)
47 | // color #fff
48 | // font-size 1.5em
49 | // width 100px
50 | // height 100px
51 | // margin 0 auto
52 | // line-height 100px
53 | // text-align center
54 | // position relative
55 | // &:before, &:after
56 | // content ""
57 | // position absolute
58 | // width 100%
59 | // height 100%
60 | // top 0
61 | // left 0
62 | // z-index -1
63 | // // background rgba(32, 178, 170, .5)
64 | // &:before
65 | // transform rotate(8deg)
66 | // &:after
67 | // transform rotate(16deg)
68 | .ng-view
69 | &.pushed
70 | position relative
71 | width calc(100% \- 200px)
72 |
73 | .global_editor
74 | .ng-view.pushed
75 | width calc(100% \- 200px \- 64px)
76 |
77 | .header, .ng-view
78 | transition left .3s
79 | &.pushed
80 | left 200px
--------------------------------------------------------------------------------
/assets/stylus/mod_auth.styl:
--------------------------------------------------------------------------------
1 | @import "lib/vendor"
2 |
3 | .auth
4 | margin-top 10%
5 | // transform(translateY(-50%))
6 | text-align center
7 |
--------------------------------------------------------------------------------
/assets/stylus/mod_friends.styl:
--------------------------------------------------------------------------------
1 | #friends
2 | .friends
3 | padding 0
4 | margin 0
5 | .friend
6 | display inline-block
7 | margin 1em
8 | width 60px
9 | text-align center
10 | vertical-align top
11 | a
12 | color #999
13 | text-decoration none
14 | &:hover
15 | opacity .8
16 | span
17 | display block
18 | margin-top .3em
19 | img
20 | border-radius 24px
21 | box-shadow 0 1px 3px #aaa
22 | &.more
23 | font-size 1.5em
24 | line-height 2em
25 | a
26 | color lightseagreen
27 | &:hover
28 | opacity .8
--------------------------------------------------------------------------------
/assets/stylus/mod_info.styl:
--------------------------------------------------------------------------------
1 | @require 'lib/vendor'
2 |
3 | #info
4 | margin-bottom 3em
5 | .summary
6 | white-space pre-wrap
7 | line-height 1.8
8 | a
9 | color lightseagreen
10 | text-decoration none
11 | margin 0 .3em
12 | &:hover
13 | background lightseagreen
14 | color #fff
15 | .books
16 | .add-note
17 | font-size 1em
18 | & > section
19 | h1
20 | font-size 1.32em
21 | &.statistic
22 | h1
23 | margin-top 0
24 | &.account
25 | .ui-card
26 | text-align center
27 | margin-right 1em
28 | a
29 | font-size 3em
30 | // color #4B4D4E
31 | color #757778
32 | &:hover, &.active
33 | // color #5FB336
34 | color lightseagreen
35 | &.active
36 | cursor default
37 | span
38 | font-size .4em
39 | display block
40 | .bind-choice
41 | .icon-evernote
42 | color lightgray
43 | float left
44 | font-size 3em
45 | margin-right .3em
46 | a
47 | &:first-of-type
48 | display inline-block
49 | padding-bottom .1em
50 | margin-bottom .2em
51 | border-bottom 1px solid #eee
52 | &:last-of-type
53 | display block
54 | .unbind
55 | position relative
56 | &:before, &:after
57 | content ""
58 | position absolute
59 | &:before
60 | transition background .3s
61 | &:after
62 | content "解绑"
63 | color #e74c3c
64 | top 50%
65 | left 50%
66 | transform(translate(-50%, -50%))
67 | opacity 0
68 | transition opacity .3s
69 | &:hover
70 | cursor pointer
71 | &:before
72 | top 0
73 | left 0
74 | right 0
75 | bottom 0
76 | background rgba(255, 255, 255, .7)
77 | &:after
78 | display initial
79 | opacity 1
80 | .options
81 | .tip
82 | font-size 12px
83 | color #bbb
--------------------------------------------------------------------------------
/assets/stylus/note.styl:
--------------------------------------------------------------------------------
1 | @require "fonticon"
2 | @require 'ui/typography'
3 |
4 | body
5 | & > .meta
6 | @extends $center
7 |
8 | .global_note
9 | .ng-view
10 | margin-top 0
11 | padding-top 50px
12 |
13 | #one-note
14 | margin 3em 0
15 | h1
16 | @extends $center
17 | pre
18 | background #eee
19 | white-space pre-wrap
20 | .content
21 | margin-bottom 40px
22 | -webkit-user-select text
23 | user-select text
24 | .share
25 | margin-bottom 60px
26 |
27 | .meta
28 | color #aaa
29 | @extends $center
30 | span
31 | margin-left .5em
32 | &:first-child
33 | margin-left 0
34 | a
35 | text-decoration none
36 | color lightseagreen
37 | margin-right .5em
38 | &:hover
39 | background lightseagreen
40 | color #fff
41 |
42 | .operate
43 | position: absolute
44 | top 0
45 | right 0
46 | padding 1em
47 | margin 0 auto
48 | padding-left calc(50% \- 300px)
49 | margin .3em 0 1.2em
50 | transition opacity .3s, top .5s cubic-bezier(0.55, 0.085, 0.68, 0.53)
51 | opacity 1
52 | a
53 | color #808080
54 | margin-right 1em
55 | text-decoration none
56 | vertical-align: middle
57 | &:before
58 | margin-right: 0.3em
59 | &:hover
60 | color #111
61 | &.icon-evernote
62 | &:hover
63 | color #48b449
64 | &.icon-pen
65 | &:before
66 | vertical-align: middle
67 | &:hover
68 | color #0E90D2
69 | &.icon-star, &.icon-star-empty
70 | &:hover
71 | color #f39c12
72 | &.icon-delete
73 | &:before
74 | vertical-align middle
75 | &:hover
76 | color #DD514C
77 | &.hide
78 | top -2em
79 | opacity 0
80 |
--------------------------------------------------------------------------------
/assets/stylus/style.styl:
--------------------------------------------------------------------------------
1 | @require "lib/vendor"
2 | $page_width = 600px
3 |
4 | html, body
5 | height 100%
6 |
7 | .ng-view
8 | height calc(100% \- 50px)
9 | margin-top 50px
10 | overflow-y auto
11 |
12 | .ng-hide
13 | display: none !important
14 |
15 | body
16 | padding: 50px
17 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif
18 | background #f9f9f9
19 | color #111
20 | a
21 | color #00B7FF
22 | cursor pointer
23 |
24 | button
25 | cursor pointer
26 |
27 | // [link]
28 |
29 | pre
30 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif
31 |
32 | @import "ui/reset.styl"
33 | @import "ui/button.styl"
34 | @import "ui/form.styl"
35 | @import "ui/modal.styl"
36 | @import "ui/bar.styl"
37 | @import "ui/loading.styl"
38 | @import "ui/alert.styl"
39 | @import "ui/card.styl"
40 |
41 | header
42 | &.header
43 | bar()
44 | span.icon-sidebar
45 | float left
46 | &.vanished
47 | right auto
48 | box-shadow none
49 | background none
50 | color #aaa
51 | .title
52 | font-size .8em
53 | line-height 1.25
54 | margin-left 1em
55 | font-family Hiragino Sans GB, STHeiti, Microsoft Yahei, sans-serif
56 | a
57 | color #fff
58 |
59 | @import "mod_aside.styl"
60 | @import "mod_auth.styl"
61 |
62 | //#all-books, #one-book, #about, #info, #friends
63 | .ng-view > section
64 | margin 3em 3em 0
65 |
66 | // all books
67 |
68 | ul.books
69 | padding 0
70 | margin 0 -1em
71 |
72 | li.book
73 | margin 1em
74 |
75 | @import "widgets/book.styl"
76 |
77 | .modal-footer
78 | div:first-child
79 | float left
80 | text-align left
81 | color #999
82 |
83 | #new-book-name
84 | border 1px solid #eee
85 | padding .6em 1em
86 | box-sizing border-box
87 | outline 0
88 | width 100%
89 | &:focus
90 | border-color #999
91 | .tip
92 | color #999
93 | text-align right
94 | margin-top 1em
95 | & + .tip
96 | margin-top 0
97 | margin-bottom 1em
98 | span
99 | color rgb(237, 20, 61)
100 | // search result
101 | .search-results
102 | margin-top 1em
103 | padding 0
104 | margin-right -20px
105 | padding-right 20px
106 | max-height 260px
107 | overflow-y scroll
108 | li
109 | list-style none
110 | overflow hidden
111 | padding 8px
112 | position relative
113 | &:before, &.active &:before
114 | content ""
115 | position absolute
116 | top 0
117 | right 0
118 | bottom 0
119 | left 0
120 | background rgba(0, 0, 0, .15)
121 | border-radius 3px
122 | transform(translateZ(0))
123 | &.active
124 | &:before
125 | border 3px solid rgb(32, 178, 170)
126 | background rgba(0, 0, 0, .05)
127 | &:before
128 | display none
129 | img
130 | width 120px
131 | float left
132 | margin-right 8px
133 | .result-info
134 | // display inline-block
135 | h4
136 | word-break break-word
137 | margin 0
138 | p
139 | color #666
140 | font-size .8em
141 | &:hover, &.active
142 | &:before
143 | display block
144 |
145 | .selected-book-name
146 | color #e74c3c
147 |
148 |
149 | // one book
150 | .ng-view.pushed
151 | .main
152 | top 60px
153 |
154 | #one-book
155 | h1
156 | a
157 | color #000
158 | text-decoration none
159 |
160 | .cubic-view
161 | float left
162 |
163 | .main
164 | margin-left 310px
165 | transform(translateZ(0))
166 |
167 | header
168 | padding-bottom 1em
169 | border-bottom 1px solid #eee
170 | margin-bottom 1em
171 | span
172 | color #999
173 | margin-right 1em
174 | font-size 12px
175 | .button-default
176 | color #000
177 | border-radius 3px
178 | margin-right 1em
179 | .order
180 | display inline-block
181 | .button-primary
182 | display inline-block
183 | border-radius 2px
184 | margin-right .5em
185 | background #aaa
186 | border none
187 | border-bottom 3px solid #777
188 | transition all .3s
189 | &:before
190 | vertical-align middle
191 | &:hover
192 | background lightseagreen
193 | border-radius 2px
194 | border-color #12837D
195 | [class*="icon-"]:before
196 | margin-right .5em
197 | .right-actions
198 | float right
199 | margin-top .2em
200 | margin-bottom 1em
201 |
202 | @import "widgets/book-3d.styl"
203 |
204 | .cubic-view
205 | .book-back
206 | width 308px
207 |
208 | ul.notes
209 | padding 0
210 | @import "widgets/note-card.styl"
211 |
212 | .modal-export
213 | h3
214 | margin-bottom 0
215 | button.icon-evernote
216 | background #c0c0c0
217 | border 0
218 | color #FFF
219 | padding .5em 1em
220 | font-size 1em
221 | outline 0
222 | border-radius .3em
223 | transition background-color .2s
224 | &:before
225 | margin-right .3em
226 | &:hover
227 | background #119E4D
228 | box-shadow inset 0 1px 3px rgba(0, 0, 0, 0.3)
229 | &:before
230 | display inline-block
231 | transform(rotate(-10deg))
232 | .mask-evernote
233 | background rgba(0, 0, 0, .3)
234 | &.modal .modal-dialog
235 | min-width 70%
236 |
237 |
238 | .export-preview
239 | font-size 90%
240 | white-space pre-wrap
241 | overflow-y scroll
242 | max-height 300px
243 | margin-top 1em
244 | // box-shadow inset 0 0 2px #ccc
245 | border 2px solid #eee
246 | padding 1em
247 | box-sizing border-box
248 | -webkit-user-select text
249 | user-select text
250 | pre
251 | white-space pre-wrap
252 |
253 |
254 | // one note
255 | @import "note.styl"
256 | .modal-evernote
257 | h3
258 | margin-bottom 0
259 | .row
260 | height 2em
261 | label
262 | width 10em
263 | display inline-block
264 | vertical-align top
265 | select, input[type="text"]
266 | margin 0
267 | select
268 | font-size 1.2em
269 | input[type="text"]
270 | border 1px solid #ccc
271 | padding .4em 1em
272 | outline 0
273 | &:focus
274 | border-color #999
275 |
276 | // editor
277 | @import "edit.styl"
278 |
279 | // about
280 | @import "mod_about.styl"
281 |
282 | // info
283 | @import "mod_info.styl"
284 |
285 | // friends
286 | @import "mod_friends.styl"
287 |
288 | // mod_error
289 | .not_found
290 | font-size 2em
291 | position absolute
292 | top 30%
293 | left 50%
294 | transform(translate(-50%, -50%))
295 | color #aaa
296 | text-align center
297 | a
298 | display block
299 | font-size .8em
300 | margin-top 1em
301 |
302 | // mod_fav
303 | .footnote
304 | margin-top 1em
305 |
--------------------------------------------------------------------------------
/assets/stylus/ui/alert.styl:
--------------------------------------------------------------------------------
1 | // get colors from
2 | // http://flatuicolors.com/
3 | @import '../lib/vendor'
4 |
5 | .alert
6 | position fixed
7 | top 4em
8 | // z-index 1111
9 | width 60%
10 | min-width 500px
11 | left 20%
12 | opacity .96
13 | box-shadow 0px 2px 4px rgba(0, 0, 0, 0.2)
14 | z-index 9999
15 | // transform(rotateX(90deg))
16 | // transform-origin(top center)
17 | // transition-transform(1s)
18 | top -4em
19 | transition(top 1s)
20 | cursor pointer
21 | &:after
22 | content "x"
23 | position absolute
24 | right 1em
25 | top .5em
26 | &.show
27 | top 4em
28 | //transform(rotateX(0))
29 |
30 |
31 | .alert
32 | padding .6em 1.2em
33 | border 1px solid
34 | border-radius 2px
35 | text-align center
36 | &.success
37 | background-color #1abc9c
38 | border-color #16a085
39 | color #fff
40 | a
41 | color #fff
42 | &:hover
43 | background-color #fff
44 | color #1abc9c
45 | &.error
46 | background-color #F17288
47 | border-color #e74c3c
48 | color #fff
49 | a
50 | color #fff
51 | &:hover
52 | color #F17288
53 | background-color #fff
54 | &.warn
55 | background-color #F9EDBE
56 | border-color #F0C36D
57 | color #333
58 | a
59 | color #f39c12
60 | &:hover
61 | background-color #f39c12
62 | color #fff
--------------------------------------------------------------------------------
/assets/stylus/ui/bar.styl:
--------------------------------------------------------------------------------
1 | bar()
2 | // background linear-gradient(to right,rgba(243, 123, 124, 0.96) 0,rgba(245, 154, 133, 0.96) 100%)
3 | padding .5em
4 | font-size 1.8em
5 | position fixed
6 | top 0
7 | left 0
8 | right 0
9 | z-index 999
10 | box-shadow 0 1px 1px rgba(0, 0, 0, .1)
11 | // background linear-gradient(to left, rgba(95, 158, 160, .96) 0, rgba(126, 180, 142, .96) 100%)
12 | background rgba(95, 158, 160, .96)
13 | color rgba(255, 255, 255, .9)
14 | line-height 0
--------------------------------------------------------------------------------
/assets/stylus/ui/button.styl:
--------------------------------------------------------------------------------
1 | button
2 | &[disabled]
3 | cursor not-allowed
4 |
5 | .button-plain
6 | background #eee
7 | color #777
8 | padding .4em 1.2em
9 | border 0
10 | box-shadow 0 0 1px #ccc
11 | border-radius 3px
12 | &.active, &:active, &:focus
13 | outline 0
14 | box-shadow 0 0 1px #ccc, inset 0 0 1px #ccc
15 | background #fcfcfc
16 | color #333
17 |
18 | $basic_button
19 | padding .4em 1.2em
20 | border 0
21 | outline 0
22 | border-radius 0
23 | box-shadow none
24 | cursor pointer
25 | &:active, &:focus
26 | box-shadow inset 0 1px 2px rgba(0, 0, 0, .3)
27 | &:hover
28 | opacity .9
29 |
30 | .button-default
31 | @extends $basic_button
32 | background #fff
33 | border 1px solid #eee
34 |
35 | .button-primary
36 | @extends $basic_button
37 | background lightseagreen
38 | border 1px solid lightseagreen
39 | color #fff
40 |
41 | .button-warning
42 | @extends $basic_button
43 | background #e74c3c
44 | color #fff
45 | border 1px solid #e74c3c
46 |
47 | .button-radio
48 | background #eee
49 | @extends $basic_button
50 | border-radius 3px
51 | &.active, &:active, &:focus
52 | box-shadow inset 0 1px 3px darken(lightseagreen, 30%)
53 | color #fff
54 | background lightseagreen
55 |
56 |
57 | .button-group
58 | button
59 | &:first-child
60 | border-radius 3px 0 0 3px
61 | &.active, &:active, &:focus
62 | box-shadow 0 0 1px #ccc, inset 0 0 1px #ccc, inset 1px 0 #ddd
63 | &:last-child
64 | border-radius 0 3px 3px 0
65 | &.active, &:active, &:focus
66 | box-shadow 0 0 1px #ccc, inset 0 0 1px #ccc, inset -1px 0 #ddd
67 |
68 | .buttons-group
69 | button
70 | &:first-child
71 | margin-right 1em
--------------------------------------------------------------------------------
/assets/stylus/ui/card.styl:
--------------------------------------------------------------------------------
1 | .ui-card
2 | padding 1.5em 2em
3 | background #fff
4 | box-shadow 0 0 3px #ccc
5 | color #333
6 | display inline-block
--------------------------------------------------------------------------------
/assets/stylus/ui/form.styl:
--------------------------------------------------------------------------------
1 | input
2 | &.ng-invalid
3 | color crimson !important
--------------------------------------------------------------------------------
/assets/stylus/ui/loading.styl:
--------------------------------------------------------------------------------
1 | @import "../lib/vendor"
2 |
3 | .loading
4 | width 50px
5 | height 30px
6 | text-align center
7 | font-size 10px
8 | position fixed
9 | // top 5px
10 | // right 5px
11 | top 50%
12 | left 50%
13 | transform(translate(-50%, -50%))
14 | background rgba(0,0,0,.4)
15 | padding 80px
16 | border-radius 10px
17 | z-index 1099
18 | opacity .8
19 | transition(opacity 1s)
20 | &.fadeOut
21 | opacity 0
22 |
23 |
24 | .loading > div
25 | background-color #fff
26 | height 100%
27 | width 6px
28 | margin-right 2px
29 | display inline-block
30 | -webkit-animation stretchdelay 1.2s infinite ease-in-out
31 | animation stretchdelay 1.2s infinite ease-in-out
32 | transform translateZ(0)
33 |
34 |
35 | .loading .rect2
36 | -webkit-animation-delay -1.1s
37 | animation-delay -1.1s
38 |
39 |
40 | .loading .rect3
41 | -webkit-animation-delay -1.0s
42 | animation-delay -1.0s
43 |
44 |
45 | .loading .rect4
46 | -webkit-animation-delay -0.9s
47 | animation-delay -0.9s
48 |
49 |
50 | .loading .rect5
51 | -webkit-animation-delay -0.8s
52 | animation-delay -0.8s
53 |
54 | @keyframes stretchdelay
55 | 0%, 40%, 100%
56 | transform(scaleY(.4))
57 | 20%
58 | transform(scaleY(1.0))
--------------------------------------------------------------------------------
/assets/stylus/ui/modal.styl:
--------------------------------------------------------------------------------
1 | @import "../lib/vendor";
2 |
3 | $zindex-modal-background ?= 1040;
4 | // Modals
5 | // -------------------------
6 | $modal-inner-padding ?= 20px;
7 |
8 | $modal-title-padding ?= 15px;
9 | $modal-title-line-height ?= $line-height-base;
10 |
11 | $modal-content-bg ?= #fff;
12 | $modal-content-border-color ?= rgba(0,0,0,.2);
13 | $modal-content-fallback-border-color ?= #999;
14 |
15 | $modal-backdrop-bg ?= rgba(0, 0, 0, .3);
16 | $modal-header-border-color ?= #e5e5e5;
17 | $modal-footer-border-color ?= $modal-header-border-color;
18 | //
19 | // Modals
20 | // --------------------------------------------------
21 |
22 | // .modal-open - body class for killing the scroll
23 | // .modal - container to scroll within
24 | // .modal-dialog - positioning shell for the actual modal
25 | // .modal-content - actual modal w/ bg and corners and shit
26 |
27 | // Kill the scroll on the body
28 | .modal-open {
29 | overflow: hidden;
30 |
31 |
32 | // Account for hiding of scrollbar
33 | body&,
34 | .navbar-fixed-top,
35 | .navbar-fixed-bottom {
36 | // margin-right: 15px
37 | }
38 | }
39 |
40 | // Container that the modal scrolls within
41 | .modal
42 | display: none;
43 | overflow: auto;
44 | overflow-y: scroll;
45 | position: fixed;
46 | top: 0;
47 | right: 0;
48 | bottom: 0;
49 | left: 0;
50 | z-index: $zindex-modal-background;
51 |
52 | // When fading in the modal, animate it to slide down
53 | &.fade .modal-dialog
54 | // transform(translate(0, -25%));
55 | position absolute
56 | // top 35%
57 | top 50%
58 | left 50%
59 | // transform(translate(-50%, -50%));
60 | transform(translate(-50%, -50%))
61 | transition-transform(0.3s ease-out);
62 | min-width 60%
63 |
64 | &.in
65 | .modal-dialog
66 | translate(0, 0)
67 |
68 | // Shell div to position the modal with bottom padding
69 | .modal-dialog {
70 | margin-left: auto;
71 | margin-right: auto;
72 | width: auto;
73 | padding: 10px;
74 | z-index: ($zindex-modal-background + 10);
75 | }
76 |
77 | // Actual modal
78 | .modal-content {
79 | position: relative;
80 | background-color: $modal-content-bg;
81 | border-radius: 5px;
82 | box-shadow: 0 0px 40px rgba(0,0,0,.6);
83 | overflow: hidden
84 | background-clip: padding-box;
85 | // Remove focus outline from opened modal
86 | outline: none;
87 | }
88 |
89 | // Modal background
90 | .modal-backdrop
91 | position: fixed;
92 | top: 0;
93 | right: 0;
94 | bottom: 0;
95 | left: 0;
96 | z-index: ($zindex-modal-background - 10);
97 | background-color: $modal-backdrop-bg;
98 | // Fade for backdrop
99 | &.fade
100 | opacity(0);
101 | &.in
102 | opacity(.5);
103 |
104 | // Modal header
105 | // Top section of the modal w/ title and dismiss
106 | .modal-header {
107 | padding: $modal-title-padding;
108 | // border-bottom: 1px solid $modal-header-border-color;
109 | // min-height: ($modal-title-padding + $modal-title-line-height);
110 | }
111 | // Close icon
112 | .modal-header .close {
113 | margin-top: -2px;
114 | }
115 |
116 | // Title text within header
117 | .modal-title {
118 | margin: 0;
119 | line-height: $modal-title-line-height;
120 | }
121 |
122 | // Modal body
123 | // Where all modal content resides (sibling of .modal-header and .modal-footer)
124 | .modal-body {
125 | position: relative;
126 | padding: $modal-inner-padding;
127 | }
128 |
129 | // Footer (for actions)
130 | .modal-footer {
131 | margin-top: 15px;
132 | padding: ($modal-inner-padding - 1) $modal-inner-padding $modal-inner-padding;
133 | text-align: right; // right align buttons
134 | border-top: 1px solid $modal-footer-border-color;
135 | background: #f9f9f9;
136 | // clearfix(); // clear it in case folks use .pull-* classes on buttons
137 |
138 | // Properly space out buttons
139 | .btn + .btn {
140 | margin-left: 5px;
141 | margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
142 | }
143 | // but override that for button groups
144 | .btn-group .btn + .btn {
145 | margin-left: -1px;
146 | }
147 | // and override it for block buttons as well
148 | .btn-block + .btn-block {
149 | margin-left: 0;
150 | }
151 | }
152 |
153 | // Scale up the modal
154 | @media $media-screen-min-tablet {
155 |
156 | .modal-dialog {
157 | left: 50%;
158 | right: auto;
159 | width: 600px;
160 | padding-top: 30px;
161 | padding-bottom: 30px;
162 | }
163 | .modal-content {
164 | box-shadow 0 5px 15px rgba(0,0,0,.5);
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/assets/stylus/ui/reset.styl:
--------------------------------------------------------------------------------
1 | html, body
2 | padding 0
3 | margin 0
4 |
5 | button
6 | margin 0
7 |
8 | input::-webkit-outer-spin-button,
9 | input::-webkit-inner-spin-button
10 | -webkit-appearance none
11 | margin 0
--------------------------------------------------------------------------------
/assets/stylus/ui/typography.styl:
--------------------------------------------------------------------------------
1 | $center
2 | max-width 600px
3 | margin-left auto
4 | margin-right auto
5 |
6 | div, ul, li, p, article, h1, h2, h3, h4, h5, h6
7 | header, nav, form
8 | word-wrap break-word
9 |
10 | .typo-center
11 | @extends $center
12 |
13 | .content
14 | & > h2, & > h3, & > h4, & > h5, & > h6,
15 | & > p, & > ul, & > ol,
16 | & > pre code, & > blockquote,
17 | & > hr
18 | @extends $center
19 |
20 | .content
21 | p, & > ol, & > ul, blockquote
22 | font-size 15px
23 | line-height 1.65
24 | hr
25 | border 0
26 | border-top 1px dashed #aaa
27 | margin-top 3em
28 | margin-bottom 3em
29 | a
30 | text-decoration none
31 | color #00B7FF
32 | &:hover
33 | background-color #00B7FF
34 | color #fff
35 | img
36 | max-width 100%
37 | blockquote
38 | //background #fdfdfd
39 | font-size 16px
40 | // padding 1.6em 1.8em 1.6em 3.3em
41 | padding 0.8em 0 .8em 3em
42 | //box-shadow 0 0 2px #ccc
43 | // border-left 3px solid #ccc
44 | // border-top 1px solid #eee
45 | // border-bottom 1px solid #eee
46 | // border 1px solid #eee
47 | box-sizing border-box
48 | position relative
49 | // background #fcfcfc
50 | // background #f1f1f1
51 | // border-radius 3px
52 | // box-shadow 1px 1px 0 #ccc
53 | border-left 3px solid #ccc
54 | overflow hidden
55 | font-style italic
56 | &:before
57 | // content "“"
58 | position absolute
59 | top .2em
60 | left -.1em
61 | font-size 3em
62 | width 1em
63 | height 1em
64 | border-radius 1em
65 | line-height 1.5em
66 | text-align center
67 | color #FFF
68 | background-color #dedede
69 | font-style normal
70 | // background-color #cadab8
71 | pre
72 | padding 1em calc(50% \- 300px)
73 | code
74 | background #eee
75 | border 1px solid #ccc
76 | border-radius 3px
77 | padding 0 .3em
78 |
79 | pre
80 | background transparent
81 | code.hljs
82 | background transparent
83 |
--------------------------------------------------------------------------------
/assets/stylus/widgets/book-3d.styl:
--------------------------------------------------------------------------------
1 | @import '../lib/vendor'
2 |
3 | .book-large
4 | display inline-block
5 | box-shadow 10px 4px 5px rgba(0, 0, 0, .08), -10px 20px 12px rgba(0, 0, 0, .15) //, inset 0 0 1px #000
6 | position relative
7 | font-size 0
8 | &:before
9 | content ""
10 | position absolute
11 | top 1px
12 | bottom 1px
13 | left 1%
14 | width 1px
15 | box-shadow 10px 0 6px #111
16 | img
17 | position relative
18 | z-index -1
19 |
20 | cube_l = 300px
21 | cube_w = 40px
22 | cube_h = 400px
23 |
24 | .cubic-view
25 | perspective 1800px
26 |
27 | .book-cube
28 | transform-style preserve-3d
29 | position relative
30 | transform-origin top left
31 | transition -webkit-transform .5s
32 | transition -moz-transform .5s
33 | transition transform .5s
34 | min-width cube_l
35 | min-height cube_h
36 | display inline-block
37 | transform rotateY(0) scale(.9)
38 | &.sided
39 | transform rotateY(35deg) scale(.9)
40 | &:hover
41 | transform rotateY(0) scale(.9)
42 | div
43 | position absolute
44 | // background-color rgba(0, 0, 0, .3)
45 | .book-front, .book-back
46 | width cube_l
47 | height cube_h
48 | .book-left, .book-right
49 | width cube_w
50 | height cube_h
51 | left -(cube_w/2)
52 | .book-top, .book-bottom
53 | width cube_l - 5px
54 | height cube_w
55 | top -15px
56 | .book-back
57 | transform rotateY(-180deg) translate3d(0,0,20px)
58 | .book-right
59 | transform rotateY(90deg) translate3d(0, 0, 295px)
60 | .book-left
61 | transform rotateY(-90deg)
62 | .book-top
63 | transform rotateX(90deg)
64 | .book-bottom
65 | transform rotateX(-90deg) translate3d(0,0,390px)
66 | .book-front
67 | transform translate3d(0,0,20px)
68 | .book-front, .book-left
69 | background-size cover
70 | .book-front
71 | box-shadow inset 4px 0 10px rgba(0, 0, 0, 0.1)
72 | -webkit-transform-style preserve-3d
73 | -webkit-backface-visibility hidden
74 | &:after
75 | content ""
76 | position absolute
77 | top 0
78 | left 10px
79 | bottom 0
80 | width 3px
81 | background rgba(0, 0, 0, 0.06)
82 | box-shadow 1px 0 3px rgba(255, 255, 255, 0.1)
83 | & > div
84 | position absolute
85 | width 100%
86 | height 100%
87 | -webkit-transform-style preserve-3d
88 | -webkit-backface-visibility hidden
89 | .book-cover-frontface
90 | background-size cover
91 | background-position center
92 | .book-cover-backface
93 | transform(rotateY(180deg))
94 | .book-back
95 | box-shadow 10px 10px 30px rgba(0, 0, 0, 0.3)
96 | .book-left
97 | background-color #fff
98 | overflow hidden
99 | &:before
100 | content ""
101 | position absolute
102 | width cube_w
103 | height cube_h
104 | top 0
105 | left 0
106 | background-color rgba(0, 0, 0, .2)
107 | backface-visibility hidden
108 | .book-title
109 | -webkit-writing-mode vertical-rl
110 | width cube_w
111 | line-height cube_w
112 | font-size 1.8em
113 | //white-space pre
114 | // padding 1em 0
115 | height "calc(%s - 2em)" % cube_h
116 | overflow hidden
117 | position relative
118 | top 1em
119 | .book-page
120 | width cube_l
121 | height cube_h
122 | background #fff
123 | // line-height cube_h
124 | text-align center
125 | transform translate3d(0,0,19px)
126 | background #f1f1f1
127 | .page-content
128 | opacity 0
129 | position relative
130 | top 50%
131 | left 50%
132 | width 50%
133 | transform translate(-50%, -50%)
134 | transition opacity 1s
135 | .summary
136 | color #666
137 | margin 0 -2em 1em
138 | text-align left
139 | overflow hidden
140 | text-overflow ellipsis
141 | display -webkit-box
142 | -webkit-line-clamp 5
143 | -webkit-box-orient vertical
144 | a
145 | display block
146 | color #666
147 | // color lightseagreen
148 | padding .8em 1.2em
149 | // border 2px solid lightseagreen
150 | text-decoration none
151 | margin-bottom 1em
152 | transition all .5s
153 | border-radius 3px
154 | &:hover
155 | // background lightseagreen
156 | color #fff
157 | background #222
158 | .book-left
159 | .book-title
160 | opacity 1
161 | transition opacity 1s
162 | .book-front
163 | transition-transform 1s
164 | transform-origin top left
165 | cursor pointer
166 | transform-style preserve-3d
167 | &.opened
168 | transform translate3d(0,0,20px) rotateY(-100deg)
169 | & + .book-page
170 | .page-content
171 | opacity 1
172 |
--------------------------------------------------------------------------------
/assets/stylus/widgets/book.styl:
--------------------------------------------------------------------------------
1 | @import '../lib/vendor'
2 |
3 | book_w = 120px
4 | book_h = 160px
5 |
6 | .book
7 | display inline-block
8 | vertical-align top
9 | .cover
10 | position relative
11 | min-width book_w
12 | height book_h
13 | overflow hidden
14 | background-size cover
15 | background-position center
16 | box-shadow 0 2px 3px #ddd
17 | position relative
18 | border-radius: 2px
19 | // &:before
20 | // content ""
21 | // position absolute
22 | // top 0
23 | // bottom 0
24 | // left 0
25 | // width 10px
26 | // background linear-gradient(to right,rgb(255, 255, 255) 0,rgba(255, 255, 255, 0.1) 20%,rgba(0, 0, 0, 0.3) 100%)
27 | // opacity .3
28 | a
29 | text-decoration initial
30 | .book-link
31 | min-width book_w
32 | height book_h
33 | display inline-block
34 | // overflow hidden
35 | // border-radius 0 .3em .3em 0
36 | // box-shadow 2px 2px 2px #DDD, inset 4px 0 0 rgba(0, 0, 0, .1)
37 | box-shadow inset 0 0 1px rgba(#32373A, 0.5), inset 2px 0 5px rgba(#fff, 0.5)
38 | border-radius: 2px
39 | position relative
40 | background-color transparent
41 | transition background-color .3s
42 | &:hover
43 | a
44 | box-shadow initial
45 | .book-link
46 | background-color rgba(0, 0, 0, .65)
47 | .add-note
48 | display block
49 | bottom .7em
50 | opacity 1
51 | .notes-count
52 | top 0
53 | opacity 1
54 | .title, .author
55 | display block
56 | width book_w
57 | margin-top .3em
58 | .title
59 | font-weight bold
60 | .author
61 | font-size .9em
62 | color #ccc
63 | .add-note
64 | border 2px solid #fff
65 | color #fff
66 | font-size 1.5em
67 | text-align center
68 | line-height 1.8em
69 | border-radius 999px
70 | position absolute
71 | z-index 2
72 | bottom -3em
73 | opacity 0
74 | transition bottom .5s, opacity .2s
75 | padding 0 0.5em
76 | left 50%
77 | transform translateZ(0) translateX(-50%)
78 | white-space nowrap
79 | &:hover
80 | background #fff
81 | color #111
82 | .icon-plus
83 | font-size 0.7em
84 | line-height 2.7em
85 | margin-left 0.07em
86 | vertical-align middle
87 | span
88 | font-size 0.6em
89 | margin-left .4em
90 | vertical-align middle
91 | .notes-count
92 | text-shadow 0px 0px 18px #000
93 | position absolute
94 | z-index 2
95 | color #fff
96 | display block
97 | width 100%
98 | text-align center
99 | top -6em
100 | opacity 0
101 | transition top .5s, opacity .2s
102 | transform translateZ(0)
103 | pointer-events none
104 | .count
105 | display block
106 | font-size: 3.6em
107 | font-family: Georgia
108 | .count-text
109 | font-size: 12px
110 |
111 |
112 | .book.empty
113 | .cover
114 | box-shadow initial
115 | border 3px dashed #ccc
116 | box-sizing border-box
117 | cursor pointer
118 | &:before
119 | background transparent
120 | &:after
121 | content "+"
122 | color #bbb
123 | font-size 3em
124 | position absolute
125 | top 0
126 | right 0
127 | bottom 0
128 | left 0
129 | text-align center
130 | line-height book_h - 0.5*14px
131 | // line-height "calc(%s - .5em)" % book_h
132 | &:hover
133 | border-color #aaa
134 | &:after
135 | color #999
136 |
--------------------------------------------------------------------------------
/assets/stylus/widgets/editor.styl:
--------------------------------------------------------------------------------
1 | #editor
2 | position relative
3 | form
4 | margin-bottom 100px
5 | .guide
6 | opacity 0
7 | position fixed
8 | top 0
9 | bottom 0
10 | width 20px
11 | border-left 1px solid lightpink
12 | left calc(50% \- 340px)
13 | transition opacity .3s
14 | &:before
15 | content ""
16 | position absolute
17 | top 0
18 | right 0
19 | bottom 0
20 | left -20px
21 | width 20px
22 | &:hover
23 | opacity 1
24 | cursor col-resize
25 | input
26 | border 0
27 | background none
28 | &:focus
29 | outline 0
30 |
31 | .CodeMirror pre.CodeMirror-placeholder
32 | color #999
33 |
34 |
35 | // specific form controls
36 | #title, #page
37 | display block
38 |
39 | #title
40 | font-size 2em
41 | font-weight bold
42 | margin-bottom 1em
43 | width 100%
44 | box-sizing border-box
45 |
46 | #page
47 | font-size 1.1em
48 | margin-bottom 2em
49 | color gray
50 |
51 | #privacy
52 | color gray
53 | input
54 | margin-right .5em
55 | label
56 | margin-right 1em
57 |
58 | #page_no
59 | line-height 1.65
60 | color gray
61 | vertical-align baseline
62 | min-width 12em
63 |
64 | // warning
65 | error_tip()
66 | background rgba(240, 72, 72, 0.8)
67 | color #fff
68 | padding .2em .8em
69 | border-radius 3px
70 | display inline-block
71 |
72 | .warning
73 | error_tip()
74 | opacity 1
75 | transition opacity .2s
76 | &.right
77 | transform translateX(-105%)
78 | &.hidden
79 | transform translateX(-105%)
80 | opacity 0
81 |
82 | .toolbar
83 | background #EEE
84 | padding .6em 1em
85 | border-radius .3em
86 | li
87 | display inline-block
88 | background #fcfcfc
89 | padding .4em
90 | border-radius 2px
91 | cursor pointer
92 | margin-right .6em
93 | font-size .9em
94 | position relative
95 | &:active, &.active
96 | background #ccc
97 | box-shadow inset 0 0 3px #999
98 | #upload
99 | opacity 0
100 | position absolute
101 | top 0
102 | right 0
103 | bottom 0
104 | left 0
105 |
--------------------------------------------------------------------------------
/assets/stylus/widgets/note-card.styl:
--------------------------------------------------------------------------------
1 | @require '../fonticon'
2 |
3 | paper(font_size = 1em, line_height = 1.2em, background_color = #fff)
4 | background -webkit-gradient(linear, 0 0, 0 100%, from(#d9eaf3), color-stop(4%, background_color)) 0 (line_height/4)
5 | background -webkit-linear-gradient(top, #d9eaf3 0%, background_color 8%) 0 (line_height/4)
6 | background -moz-linear-gradient(top, #d9eaf3 0%, background_color 8%) 0 (line_height/4)
7 | background -ms-linear-gradient(top, #d9eaf3 0%, background_color 8%) 0 (line_height/4)
8 | background -o-linear-gradient(top, #d9eaf3 0%, background_color 8%) 0 (line_height/4)
9 | background linear-gradient(top, #d9eaf3 0%, background_color 8%) 0 (line_height/4)
10 | background-size 100% 20px
11 | position relative
12 | &:before
13 | content ''
14 | position absolute
15 | width 4px
16 | top 0
17 | left 1.1em
18 | bottom 0
19 | border 1px solid
20 | border-color transparent #efe4e4
21 | h1, h2, h3, h4, h5
22 | line-height line_height // line_height * 2 (stylus bug?)
23 | margin (line_height/2) 0
24 | h1, h2, h3
25 | font-size 1.2em
26 | h4, h5
27 | font-size 1em
28 | font-weight normal
29 | p
30 | line-height 1.44em // line_height * 1.2
31 |
32 |
33 | ul.notes
34 | margin 0 -.8em
35 |
36 | .note
37 | display inline-block
38 | vertical-align top
39 | border-radius .2em
40 | width calc(33% \- 1.6em*4/3)
41 | // width 28% // (100% - 1.6em)/3
42 | min-width 260px
43 | margin .8em
44 | //box-sizing padding-box
45 | //background #f3f3f3
46 | box-shadow 0 0 3px #aaa
47 | paper(1em, 1.2em, #fcfcfc)
48 | // &:before
49 | // content ""
50 | // position absolute
51 | // top 0
52 | // bottom 0
53 | // width 2px
54 | // left 1em
55 | // background-color #FFB6C1 // lightpink
56 | // opacity .3
57 | &:hover
58 | .note-mask
59 | background-color rgba(0, 0, 0, .03)
60 | .note-mask
61 | display block
62 | text-decoration none
63 | color #333
64 | padding .6em 1.3em 1em 2.3em
65 | time
66 | color #aaa
67 | h3
68 | word-wrap break-word
69 | .note-summary
70 | word-wrap break-word
71 | margin 0 0 1em
72 | // margin 0
73 | color #444
74 | .note-page
75 | color #ccc
76 | font-size .8em
77 | position absolute
78 | bottom .2em
79 | right .4em
80 |
--------------------------------------------------------------------------------
/assets/stylus/widgets/reader.styl:
--------------------------------------------------------------------------------
1 | @require '../ui/typography'
2 |
3 | #reader
4 | min-height 100%
5 | z-index 2
6 | transition left 1s
7 | background #f7f7f7
8 | &.hidden
9 | left 100%
10 | h1
11 | margin-top 0
12 | @extend $center
13 |
14 | pre
15 | overflow-x scroll
16 | .content
17 | margin-bottom 100px
18 | word-break break-word
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anno",
3 | "version": "0.0.0",
4 | "authors": [
5 | "simonday@foxmail.com"
6 | ],
7 | "description": "Annotation",
8 | "main": "app.js",
9 | "keywords": [
10 | "annotation"
11 | ],
12 | "license": "MIT",
13 | "homepage": "http://daix.me",
14 | "ignore": [
15 | "**/.*",
16 | "node_modules",
17 | "bower_components",
18 | "test",
19 | "tests"
20 | ],
21 | "dependencies": {
22 | "angular-route": "~1.2.15",
23 | "angular-cookies": "~1.2.15",
24 | "angular-bootstrap": "*",
25 | "marked": "*",
26 | "codemirror": "*",
27 | "color-thief": "*",
28 | "underscore": "*",
29 | "evernote": "~1.25.2",
30 | "angular-sanitize": "~1.2.15"
31 | },
32 | "resolutions": {
33 | "angular": "~1.2.15"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anno",
3 | "version": "0.1.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "node app.js"
7 | },
8 | "devDependencies": {
9 | "grunt": "~0.4.2",
10 | "grunt-contrib-stylus": "~0.13.0",
11 | "grunt-contrib-jade": "~0.10.0",
12 | "grunt-contrib-watch": "~0.5.3",
13 | "grunt-contrib-concat": "~0.3.0",
14 | "grunt-contrib-uglify": "~0.4.0",
15 | "grunt-contrib-compress": "~0.7.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/public/background.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html', {
3 | 'minWidth': 860,
4 | 'minHeight': 580,
5 | 'bounds': {
6 | 'width': 960,
7 | 'height': 600
8 | }
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/public/css/codemirror.css:
--------------------------------------------------------------------------------
1 | /* ------------------------------------------
2 | CodeMirror
3 | ---------------------------------------------*/
4 | .CodeMirror {
5 | font-family:Menlo, monospace;
6 | font-size:13px;
7 | line-height:1.4em;
8 | -webkit-font-smoothing:subpixel-antialiased;
9 | }
10 | .code .CodeMirror-scroll {
11 | min-height:360px;
12 | }
13 | .markdown .CodeMirror {
14 | font-size:14px;
15 | line-height:1.75em
16 | }
17 |
18 | .CodeMirror-scroll {
19 | /* Set scrolling behaviour here */
20 | overflow: auto;
21 | }
22 |
23 | /* PADDING */
24 | .CodeMirror-scrollbar-filler {
25 | background-color: white; /* The little square between H and V scrollbars */
26 | }
27 |
28 | /* GUTTER */
29 | .CodeMirror-linenumbers {}
30 | .CodeMirror-linenumber {
31 | padding:0 3px 0 5px;
32 | min-width:20px;
33 | text-align:right;
34 | color:#d4d7d9;
35 | }
36 | .cm-s-prose-bright .CodeMirror-gutter {
37 | border-right:1px solid #eff3f5;
38 | padding-right:5px;
39 | margin-right:15px;
40 | min-width:2.5em;
41 | }
42 |
43 | /* CURSOR */
44 | .CodeMirror div.CodeMirror-cursor {
45 | border-left: 1px solid #3D494E;
46 | z-index: 3;
47 | }
48 | .cm-s-prose-bright .CodeMirror-cursor { border-left: 2px solid #667880; }
49 | .cm-s-prose-bright .CodeMirror-lines { margin:0; }
50 |
51 | /* Shown when moving in bi-directional text */
52 | .CodeMirror div.CodeMirror-secondarycursor {
53 | border-left: 1px solid #e0e7eb;
54 | }
55 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
56 | width: auto;
57 | border: 0;
58 | background: #90bb74;
59 | z-index: 1;
60 | }
61 | /* Can style cursor different in overwrite (non-insert) mode */
62 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
63 | .cm-tab { display: inline-block; }
64 |
65 | /* Prose Bright Theme
66 | -------------------- */
67 | /* Dark Grey */
68 | .cm-s-prose-bright { color:#3D494E; }
69 | .cm-s-prose-bright span.cm-header { color:#3D494E; font-weight:bold;}
70 | .cm-s-prose-bright span.cm-variable-2 { color:#3D494E; }
71 |
72 | /* Medium Grey */
73 | .cm-s-prose-bright span.cm-meta { color:#516066; }
74 | .cm-s-prose-bright span.cm-hr { color:#516066;}
75 |
76 | /* Lighter Grey */
77 | .cm-s-prose-bright span.cm-comment { color:#868f93; }
78 | .cm-s-prose-bright span.cm-qualifier { color:#868f93; }
79 |
80 | /* Dark Blue/Green */
81 | .cm-s-prose-bright span.cm-number { color:#197987; }
82 | .cm-s-prose-bright span.cm-variable { color:#197987; }
83 | .cm-s-prose-bright span.cm-builtin { color:#197987; }
84 | .cm-s-prose-bright span.cm-link { color:#197987; text-decoration:underline;}
85 | .cm-s-prose-bright span.cm-tag { color:#197987; }
86 |
87 | /* Medium Blue/Green */
88 | .cm-s-prose-bright span.cm-string { color:#48abb9; }
89 | .cm-s-prose-bright span.cm-string-2 { color:#48abb9; }
90 | .cm-s-prose-bright span.cm-quote { color:#48abb9;}
91 | .cm-s-prose-bright span.cm-atom { color:#48abb9; }
92 |
93 | /* Light Blue/Green */
94 | /*.cm-s-prose-bright .CodeMirror-selected { background:#90d5df; }*/
95 |
96 | /* Turtle Green */
97 | .cm-s-prose-bright span.cm-property { color:#82a367; }
98 | .cm-s-prose-bright span.cm-operator { color:#82a367; }
99 | .cm-s-prose-bright span.cm-variable-3 { color:#82a367; }
100 |
101 | /* Light Turtle Green */
102 | .cm-s-prose-bright span.cm-attribute { color:#90bb74; }
103 | .cm-s-prose-bright span.cm-def { color:#90bb74; }
104 |
105 | /* Brick */
106 | .cm-s-prose-bright span.cm-keyword { color:#ec6c45; }
107 | .cm-s-prose-bright span.cm-bracket { color:#ec6c45; }
108 |
109 | /* Darker Brick */
110 | .cm-s-prose-bright span.cm-error { color:#e45346; }
111 |
112 | .cm-s-prose-bright span.cm-em { font-style:italic; }
113 | .cm-s-prose-bright span.cm-strong { font-weight:bold; }
114 |
115 | /* STOP */
116 | /* The rest of this file contains styles related to the mechanics of
117 | the editor. You probably shouldn't touch them. */
118 |
119 | .CodeMirror {
120 | /*line-height: 1;*/
121 | position:relative;
122 | overflow:hidden;
123 | /* background:white; */
124 | color:#516066;
125 | }
126 |
127 | .CodeMirror-scroll {
128 | /* 30px is the magic margin used to hide the element's real scrollbars */
129 | /* See overflow: hidden in .CodeMirror */
130 | margin-bottom: -30px; margin-right: -30px;
131 | padding-bottom: 30px; padding-right: 30px;
132 | height: 100%;
133 | outline: none; /* Prevent dragging from highlighting the element */
134 | position: relative;
135 | }
136 | .CodeMirror-sizer {
137 | position: relative;
138 | }
139 |
140 | /* The fake, visible scrollbars. Used to force redraw during scrolling
141 | before actuall scrolling happens, thus preventing shaking and
142 | flickering artifacts. */
143 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {
144 | position: absolute;
145 | z-index: 6;
146 | display: none;
147 | }
148 | .CodeMirror-vscrollbar {
149 | right: 0; top: 0;
150 | overflow-x: hidden;
151 | overflow-y: scroll;
152 | }
153 | .CodeMirror-hscrollbar {
154 | bottom: 0; left: 0;
155 | overflow-y: hidden;
156 | overflow-x: scroll;
157 | }
158 | .CodeMirror-scrollbar-filler {
159 | right: 0; bottom: 0;
160 | z-index: 6;
161 | }
162 |
163 | .CodeMirror-gutters {
164 | position: absolute; left: 0; top: 0;
165 | height: 100%;
166 | padding-bottom: 30px;
167 | z-index: 3;
168 | }
169 | .CodeMirror-gutter {
170 | height: 100%;
171 | padding-bottom: 30px;
172 | margin-bottom: -32px;
173 | display: inline-block;
174 | /* Hack to make IE7 behave */
175 | *zoom:1;
176 | *display:inline;
177 | }
178 | .CodeMirror-gutter-elt {
179 | position: absolute;
180 | cursor: default;
181 | z-index: 4;
182 | }
183 |
184 | .CodeMirror-lines {
185 | cursor: text;
186 | }
187 | .CodeMirror pre {
188 | margin: 0;
189 | white-space: pre;
190 | word-wrap: normal;
191 | z-index: 2;
192 | position: relative;
193 | overflow: visible;
194 | }
195 | .CodeMirror-wrap pre {
196 | word-wrap: break-word;
197 | white-space: pre-wrap;
198 | word-break: normal;
199 | }
200 | .CodeMirror-linebackground {
201 | position: absolute;
202 | left: 0; right: 0; top: 0; bottom: 0;
203 | z-index: 0;
204 | }
205 |
206 | .CodeMirror-linewidget {
207 | position: relative;
208 | z-index: 2;
209 | overflow: auto;
210 | }
211 |
212 | .CodeMirror-widget {
213 | display: inline-block;
214 | }
215 |
216 | .CodeMirror-wrap .CodeMirror-scroll {
217 | overflow-x: hidden;
218 | }
219 |
220 | .CodeMirror-measure {
221 | position: absolute;
222 | width: 100%; height: 0px;
223 | overflow: hidden;
224 | visibility: hidden;
225 | }
226 | .CodeMirror-measure pre { position: static; }
227 |
228 | .CodeMirror div.CodeMirror-cursor {
229 | position: absolute;
230 | visibility: hidden;
231 | border-right: none;
232 | width: 0;
233 | }
234 | .CodeMirror-focused div.CodeMirror-cursor {
235 | visibility: visible;
236 | }
237 |
238 | .CodeMirror-selected { background: #d9d9d9; }
239 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
240 |
241 | .cm-searching {
242 | background: #ffa;
243 | background: rgba(255, 255, 0, .4);
244 | }
245 |
246 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */
247 | .CodeMirror span { *vertical-align: text-bottom; }
248 |
249 | @media print {
250 | /* Hide the cursor when printing */
251 | .CodeMirror div.CodeMirror-cursor {
252 | visibility: hidden;
253 | }
254 | }
255 |
256 |
--------------------------------------------------------------------------------
/public/css/themes/solarized_dark.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | padding: 0.5em;
10 | background: #002b36;
11 | color: #839496;
12 | }
13 |
14 | .hljs-comment,
15 | .hljs-template_comment,
16 | .diff .hljs-header,
17 | .hljs-doctype,
18 | .hljs-pi,
19 | .lisp .hljs-string,
20 | .hljs-javadoc {
21 | color: #586e75;
22 | }
23 |
24 | /* Solarized Green */
25 | .hljs-keyword,
26 | .hljs-winutils,
27 | .method,
28 | .hljs-addition,
29 | .css .hljs-tag,
30 | .hljs-request,
31 | .hljs-status,
32 | .nginx .hljs-title {
33 | color: #859900;
34 | }
35 |
36 | /* Solarized Cyan */
37 | .hljs-number,
38 | .hljs-command,
39 | .hljs-string,
40 | .hljs-tag .hljs-value,
41 | .hljs-rules .hljs-value,
42 | .hljs-phpdoc,
43 | .tex .hljs-formula,
44 | .hljs-regexp,
45 | .hljs-hexcolor,
46 | .hljs-link_url {
47 | color: #2aa198;
48 | }
49 |
50 | /* Solarized Blue */
51 | .hljs-title,
52 | .hljs-localvars,
53 | .hljs-chunk,
54 | .hljs-decorator,
55 | .hljs-built_in,
56 | .hljs-identifier,
57 | .vhdl .hljs-literal,
58 | .hljs-id,
59 | .css .hljs-function {
60 | color: #268bd2;
61 | }
62 |
63 | /* Solarized Yellow */
64 | .hljs-attribute,
65 | .hljs-variable,
66 | .lisp .hljs-body,
67 | .smalltalk .hljs-number,
68 | .hljs-constant,
69 | .hljs-class .hljs-title,
70 | .hljs-parent,
71 | .haskell .hljs-type,
72 | .hljs-link_reference {
73 | color: #b58900;
74 | }
75 |
76 | /* Solarized Orange */
77 | .hljs-preprocessor,
78 | .hljs-preprocessor .hljs-keyword,
79 | .hljs-pragma,
80 | .hljs-shebang,
81 | .hljs-symbol,
82 | .hljs-symbol .hljs-string,
83 | .diff .hljs-change,
84 | .hljs-special,
85 | .hljs-attr_selector,
86 | .hljs-subst,
87 | .hljs-cdata,
88 | .clojure .hljs-title,
89 | .css .hljs-pseudo,
90 | .hljs-header {
91 | color: #cb4b16;
92 | }
93 |
94 | /* Solarized Red */
95 | .hljs-deletion,
96 | .hljs-important {
97 | color: #dc322f;
98 | }
99 |
100 | /* Solarized Violet */
101 | .hljs-link_label {
102 | color: #6c71c4;
103 | }
104 |
105 | .tex .hljs-formula {
106 | background: #073642;
107 | }
108 |
--------------------------------------------------------------------------------
/public/css/themes/tomorrow.css:
--------------------------------------------------------------------------------
1 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
2 |
3 | /* Tomorrow Comment */
4 | .hljs-comment,
5 | .hljs-title {
6 | color: #8e908c;
7 | }
8 |
9 | /* Tomorrow Red */
10 | .hljs-variable,
11 | .hljs-attribute,
12 | .hljs-tag,
13 | .hljs-regexp,
14 | .ruby .hljs-constant,
15 | .xml .hljs-tag .hljs-title,
16 | .xml .hljs-pi,
17 | .xml .hljs-doctype,
18 | .html .hljs-doctype,
19 | .css .hljs-id,
20 | .css .hljs-class,
21 | .css .hljs-pseudo {
22 | color: #c82829;
23 | }
24 |
25 | /* Tomorrow Orange */
26 | .hljs-number,
27 | .hljs-preprocessor,
28 | .hljs-pragma,
29 | .hljs-built_in,
30 | .hljs-literal,
31 | .hljs-params,
32 | .hljs-constant {
33 | color: #f5871f;
34 | }
35 |
36 | /* Tomorrow Yellow */
37 | .ruby .hljs-class .hljs-title,
38 | .css .hljs-rules .hljs-attribute {
39 | color: #eab700;
40 | }
41 |
42 | /* Tomorrow Green */
43 | .hljs-string,
44 | .hljs-value,
45 | .hljs-inheritance,
46 | .hljs-header,
47 | .ruby .hljs-symbol,
48 | .xml .hljs-cdata {
49 | color: #718c00;
50 | }
51 |
52 | /* Tomorrow Aqua */
53 | .css .hljs-hexcolor {
54 | color: #3e999f;
55 | }
56 |
57 | /* Tomorrow Blue */
58 | .hljs-function,
59 | .python .hljs-decorator,
60 | .python .hljs-title,
61 | .ruby .hljs-function .hljs-title,
62 | .ruby .hljs-title .hljs-keyword,
63 | .perl .hljs-sub,
64 | .javascript .hljs-title,
65 | .coffeescript .hljs-title {
66 | color: #4271ae;
67 | }
68 |
69 | /* Tomorrow Purple */
70 | .hljs-keyword,
71 | .javascript .hljs-function {
72 | color: #8959a8;
73 | }
74 |
75 | .hljs {
76 | display: block;
77 | background: white;
78 | color: #4d4d4c;
79 | padding: 0.5em;
80 | }
81 |
82 | .coffeescript .javascript,
83 | .javascript .xml,
84 | .tex .hljs-formula,
85 | .xml .javascript,
86 | .xml .vbscript,
87 | .xml .css,
88 | .xml .hljs-cdata {
89 | opacity: 0.5;
90 | }
91 |
--------------------------------------------------------------------------------
/public/css/themes/zenburn.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov
4 | based on dark.css by Ivan Sagalaev
5 |
6 | */
7 |
8 | .hljs {
9 | display: block; padding: 0.5em;
10 | background: #3F3F3F;
11 | color: #DCDCDC;
12 | }
13 |
14 | .hljs-keyword,
15 | .hljs-tag,
16 | .css .hljs-class,
17 | .css .hljs-id,
18 | .lisp .hljs-title,
19 | .nginx .hljs-title,
20 | .hljs-request,
21 | .hljs-status,
22 | .clojure .hljs-attribute {
23 | color: #E3CEAB;
24 | }
25 |
26 | .django .hljs-template_tag,
27 | .django .hljs-variable,
28 | .django .hljs-filter .hljs-argument {
29 | color: #DCDCDC;
30 | }
31 |
32 | .hljs-number,
33 | .hljs-date {
34 | color: #8CD0D3;
35 | }
36 |
37 | .dos .hljs-envvar,
38 | .dos .hljs-stream,
39 | .hljs-variable,
40 | .apache .hljs-sqbracket {
41 | color: #EFDCBC;
42 | }
43 |
44 | .dos .hljs-flow,
45 | .diff .hljs-change,
46 | .python .exception,
47 | .python .hljs-built_in,
48 | .hljs-literal,
49 | .tex .hljs-special {
50 | color: #EFEFAF;
51 | }
52 |
53 | .diff .hljs-chunk,
54 | .hljs-subst {
55 | color: #8F8F8F;
56 | }
57 |
58 | .dos .hljs-keyword,
59 | .python .hljs-decorator,
60 | .hljs-title,
61 | .haskell .hljs-type,
62 | .diff .hljs-header,
63 | .ruby .hljs-class .hljs-parent,
64 | .apache .hljs-tag,
65 | .nginx .hljs-built_in,
66 | .tex .hljs-command,
67 | .hljs-prompt {
68 | color: #efef8f;
69 | }
70 |
71 | .dos .hljs-winutils,
72 | .ruby .hljs-symbol,
73 | .ruby .hljs-symbol .hljs-string,
74 | .ruby .hljs-string {
75 | color: #DCA3A3;
76 | }
77 |
78 | .diff .hljs-deletion,
79 | .hljs-string,
80 | .hljs-tag .hljs-value,
81 | .hljs-preprocessor,
82 | .hljs-pragma,
83 | .hljs-built_in,
84 | .sql .hljs-aggregate,
85 | .hljs-javadoc,
86 | .smalltalk .hljs-class,
87 | .smalltalk .hljs-localvars,
88 | .smalltalk .hljs-array,
89 | .css .hljs-rules .hljs-value,
90 | .hljs-attr_selector,
91 | .hljs-pseudo,
92 | .apache .hljs-cbracket,
93 | .tex .hljs-formula,
94 | .coffeescript .hljs-attribute {
95 | color: #CC9393;
96 | }
97 |
98 | .hljs-shebang,
99 | .diff .hljs-addition,
100 | .hljs-comment,
101 | .java .hljs-annotation,
102 | .hljs-template_comment,
103 | .hljs-pi,
104 | .hljs-doctype {
105 | color: #7F9F7F;
106 | }
107 |
108 | .coffeescript .javascript,
109 | .javascript .xml,
110 | .tex .hljs-formula,
111 | .xml .javascript,
112 | .xml .vbscript,
113 | .xml .css,
114 | .xml .hljs-cdata {
115 | opacity: 0.5;
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/public/fonts/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/public/fonts/icomoon.eot
--------------------------------------------------------------------------------
/public/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/public/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/public/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/public/fonts/icomoon.woff
--------------------------------------------------------------------------------
/public/img/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/public/img/bg.png
--------------------------------------------------------------------------------
/public/img/books.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/public/img/books.png
--------------------------------------------------------------------------------
/public/img/fabric_of_squares_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/public/img/fabric_of_squares_gray.png
--------------------------------------------------------------------------------
/public/img/login_with_douban_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/public/img/login_with_douban_32.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 | 读书笔记
--------------------------------------------------------------------------------
/public/js/app.min.js:
--------------------------------------------------------------------------------
1 | "use strict";var $=function(selector){return"string"==typeof selector?angular.element(document.querySelectorAll(selector)):1===selector.nodeType?angular.element(selector):null},app=angular.module("ANNO",["ngRoute","ANNO.controllers","ANNO.directives","ANNO.ui-directives","ui.bootstrap"]).config(["$routeProvider",function($routeProvider){$routeProvider.when("/login",{templateUrl:"/partials/login.html",controller:"LoginCtrl"}).when("/",{templateUrl:"/partials/books.html",controller:"BooksCtrl"}).when("/:uid/book/:bid",{templateUrl:"/partials/book.html",controller:"BookCtrl"}).when("/:uid/book/:bid/new",{templateUrl:"/partials/edit.html",controller:"EditorCtrl"}).when("/:uid/info",{templateUrl:"/partials/info.html",controller:"InfoCtrl"}).when("/note/:nid",{templateUrl:"/partials/note.html",controller:"NoteCtrl"}).when("/note/:nid/edit",{templateUrl:"/partials/edit.html",controller:"EditorCtrl"}).when("/friends",{templateUrl:"/partials/friends.html",controller:"FriendsCtrl"}).when("/fav",{templateUrl:"/partials/fav.html",controller:"FavCtrl"}).when("/about",{templateUrl:"/partials/about.html"}).when("/:uid/",{templateUrl:"/partials/books.html",controller:"BooksCtrl"}).otherwise({templateUrl:"/partials/error.html"})}]).config(["$httpProvider",function($httpProvider){$httpProvider.interceptors.push("HttpLoadingIntercepter"),$httpProvider.interceptors.push("HttpOAuthIntercepter")}]).config(["$compileProvider",function($compileProvider){$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|blob):|data:image\//)}]).run(function($route,$rootScope,$location,UserService){$rootScope.$on("$routeChangeStart",function(event,next){"/partials/about.html"!=next.templateUrl&&"/partials/login.html"!=next.templateUrl&&UserService.isLoggedIn().catch(function(){$location.path("/login")})})});
--------------------------------------------------------------------------------
/public/js/directives.min.js:
--------------------------------------------------------------------------------
1 | "use strict";angular.module("ANNO.directives",[]).directive("inputEnter",function(){var ENTER_KEY=13;return function(scope,elem,attrs){elem.bind("keydown",function(event){event.keyCode===ENTER_KEY&&scope.$apply(attrs.inputEnter)})}}).directive("editor",["$modal",function($modal){return{restrict:"E",replace:!0,templateUrl:"/partials/widgets/editor.html",scope:{article:"=",images:"="},controller:function($scope){var utils={bold:function(s,editor){editor.replaceSelection("*"===s.charAt(0)&&s.charAt(s.length-1==="*")?s.replace(/\*/g,""):"**"+s.replace(/\*/g,"")+"**")},italic:function(s,editor){editor.replaceSelection("_"===s.charAt(0)&&s.charAt(s.length-1==="_")?s.replace(/_/g,""):"_"+s.replace(/_/g,"")+"_")},quote:function(s,editor){editor.replaceSelection(">"===s.charAt(0)?util.lTrim(s.replace(/\>/g,"")):"> "+s.replace(/\>/g,""))}},self=this;this.codeMirrorLoad=function(editor){$scope.bold=function(){""!==editor.getSelection()&&utils.bold(editor.getSelection(),editor)},$scope.italic=function(){""!==editor.getSelection()&&utils.italic(editor.getSelection(),editor)},$scope.quote=function(){""!==editor.getSelection()&&utils.quote(editor.getSelection(),editor)},self.uploadPhoto=function(dataURL,file){$scope.article.last_photo=$scope.article.last_photo||0,++$scope.article.last_photo,$scope.images.push({name:"pic"+$scope.article.last_photo,src:dataURL,obj:file}),editor.replaceSelection("<图片"+$scope.article.last_photo+">")},$scope.toggleEditorHelp=function(){$modal.open({templateUrl:"modalEditorHelp.html",controller:function($scope,$modalInstance){$scope.ok=function(){$modalInstance.close()}}})}}}}}]).directive("codemirror",function(){return{restrict:"E",require:["^editor","ngModel"],replace:!0,link:function(scope,elem,attrs,ctrls){var editorCtrl=ctrls[0],ngModel=ctrls[1],editor=new CodeMirror(elem[0],{mode:"gfm",lineWrapping:!0,lineNumbers:!1,matchBrackets:!0,autofocus:!0,theme:"prose-bright",placeholder:"开始写笔记"});editor.refresh(),editor.on("change",function(cm){var phase=scope.$root.$$phase;"$apply"==phase||"$digest"==phase?ngModel.$setViewValue(cm.getValue()):scope.$apply(function(){ngModel.$setViewValue(cm.getValue())})}),ngModel.$render=function(){ngModel.$viewValue&&editor.setValue(ngModel.$viewValue)},editorCtrl.codeMirrorLoad(editor)}}}).directive("filereader",function(){return{restrict:"A",require:"^editor",link:function(scope,elem,attrs,editorCtrl){var reader=new FileReader;reader.onload=function(e){editorCtrl.uploadPhoto(e.target.result,elem[0].files[0])},elem.on("change",function(){reader.readAsDataURL(elem[0].files[0])})}}}).directive("reader",function(){return{restrict:"E",replace:!0,templateUrl:"/partials/widgets/reader.html",scope:{article:"=",images:"="},controller:function($scope){this.images=$scope.images}}}).directive("previewer",["$timeout","$http",function($timeout,$http){var imageCache={};return{restrict:"E",scope:{markdown:"=",images:"="},template:'{{markdown}}
',replace:!0,require:"^?reader",link:function(scope,elem){var renderer=new marked.Renderer;renderer.image=function(href,title,text){var out='
":">"},renderer.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(0===prot.indexOf("javascript:"))return""}var out=''+text+""},marked.setOptions({renderer:renderer,gfm:!0,tables:!0,breaks:!0,pedantic:!1,sanitize:!0,smartLists:!0,smartypants:!1}),scope.$watch("markdown",function(text){if(!_.isUndefined(text)){elem.html(marked(text));var MIME={c:"text/x-csrc",cpp:"text/x-c++src",csharp:"text/x-csharp",java:"text/x-java",lisp:"text/x-common-lisp"};Array.prototype.slice.call(elem.find("pre"),0).forEach(function(block){var $block=$(block),$code=$block.find("code");if($code.attr("class")){var mode=$code.attr("class").slice(5);mode=MIME[mode]||mode,CodeMirror.runMode($block.text(),mode,block),$block.addClass("cm-s-prose-bright")}}),Array.prototype.slice.call(elem.find("img"),0).forEach(function(img){var $img=$(img),source=$img.attr("data-src");imageCache[source]?$img.attr("src",imageCache[source]):$http.get($img.attr("data-src"),{responseType:"blob",cache:!0}).success(function(blob){imageCache[source]=window.URL.createObjectURL(blob),Array.prototype.slice.call(elem.find("img"),0).forEach(function(new_img){$(new_img).attr("src",imageCache[source])})})}),elem.html(elem.html().replace(/\<图片(\d+)\>/g,function(wholeMatch,m1){var img=_.findWhere(scope.images,{name:"pic"+m1});return'
'}))}})}}}]).directive("loader",["$rootScope","$timeout",function($rootScope,$timeout){return function(scope,elem){scope.$on("loading:show",function(){elem.css("display","block").removeClass("fadeOut")}),scope.$on("loading:hide",function(){elem.addClass("fadeOut"),$timeout(function(){elem.css("display","none")},1e3)})}}]).directive("alert",["$rootScope","$timeout","$compile","AnalyticsService",function($rootScope,$timeout,$compile,AnalyticsService){return function(scope,elem){var ERROR={103:'访问令牌出错,需要重新授权登录',106:'访问令牌过期,需要重新登录',111:"访问太频繁,超出第三方应用限额",999:"未知错误",1000:'这篇笔记是私密的,只能阅读到简介部分,不能显示全文。点我返回书架',1001:'内容不存在,回到我的书架',1002:"必要的信息还没填完,请检查标题和内容是否填写",1003:"上传图片太大,不能大于3M",1004:"有违禁词",1005:"输入的正文太短,要求超过15个字",1008:"不支持上传图片的格式"};scope.$on("alert:error",function(e,data){var msg=ERROR[data.code];msg||(msg=data.code?"你遇到了编号为"+data.code+'的错误"'+data.msg:'发生了奇怪的错误,可能是服务器不稳定,稍后再试试吧。告诉管理员让他修复错误吧。'),elem.removeClass("success").addClass("error").addClass("show").html(msg);var link=elem.find("a");link.length&&link.replaceWith($compile(link[0].outerHTML)(scope)),AnalyticsService.reportError({id:data.code,msg:data.msg})}),scope.$on("alert:success",function(e,msg){elem.removeClass("error").addClass("success").addClass("show").html(msg),$timeout(function(){scope.$emit("alert:dismiss")},2e3)}),scope.$on("alert:dismiss",function(){elem.removeClass("show")}),elem.on("click",function(){elem.removeClass("show")}),scope.$on("alert:error:hide",elem.removeClass.bind(elem,"show")),$rootScope.$on("$routeChangeStart",scope.$emit.bind(scope,"alert:error:hide"))}}]).directive("colorThief",["$http",function($http){return{restrict:"A",require:"ngModel",link:function(scope,elem,attrs,ngModel){var colorThief=new ColorThief,sourceImage=new Image;scope.$watch(attrs.image,function(url){url&&($http.get(url,{responseType:"blob",cache:"true"}).success(function(blob){sourceImage.src=window.URL.createObjectURL(blob)}),sourceImage.onload=function(){var rgb=colorThief.getColor(sourceImage),luma=.2126*rgb[0]+.7152*rgb[1]+.0722*rgb[2];ngModel.$setViewValue({background:"rgb("+rgb.join(",")+")",foreground:luma>192?"#000":"#fff"})})})}}}]).directive("link",function($location){return function(scope,element,attrs){element.bind("click",function(){scope.$apply($location.path(attrs.link))})}}).directive("compile",["$compile",function($compile){return function(scope,element,attrs){scope.$watch(function(scope){return scope.$eval(attrs.compile)},function(value){element.html(value),$compile(element.contents())(scope)})}}]).directive("remoteImage",["$http",function($http){function fetchImage(scope,elem,url){$http.get(url,{responseType:"blob",cache:!0}).success(function(blob){"IMG"===elem[0].nodeName?elem.attr("src",window.URL.createObjectURL(blob)):elem.css({"background-image":"url("+window.URL.createObjectURL(blob)+")"})})}return{link:function(scope,elem,attrs){scope.$watch(attrs.remoteImage,function(url){url&&fetchImage(scope,elem,url)})}}}]);
--------------------------------------------------------------------------------
/public/js/douban-auth.min.js:
--------------------------------------------------------------------------------
1 | var DoubanAuth=function(){"use strict";var conf=DoubanAuthConf,redirectURL="https://"+chrome.runtime.id+".chromiumapp.org/auth/callback",redirectRe=new RegExp(redirectURL+"[#?](.*)"),access_token=null;return{getToken:function(callback){function parseRedirectFragment(fragment){var pairs=fragment.split(/&/),values={};return pairs.forEach(function(pair){var nameval=pair.split(/=/);values[nameval[0]]=nameval[1]}),values}function handleProviderResponse(values){console.log("providerResponse",values),values.hasOwnProperty("access_token")?setAccessToken(values.access_token):values.hasOwnProperty("code")?exchangeCodeForToken(values.code):callback(new Error("Neither access_token nor code avialable."))}function exchangeCodeForToken(code){var xhr=new XMLHttpRequest;xhr.open("POST","https://www.douban.com/service/auth2/token?client_id="+conf.api_key+"&client_secret="+conf.api_secret+"&redirect_uri="+redirectURL+"&grant_type=authorization_code&code="+code),xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),xhr.setRequestHeader("Accept","application/json"),xhr.onload=function(){if(200===this.status){var response=JSON.parse(this.responseText);console.log(response),response.hasOwnProperty("access_token")?setAccessToken(response.access_token):callback(new Error("Cannot obtain access_token from code."))}else console.log("code exchange status:",this.status),callback(new Error("Code exchange failed"))},xhr.send()}function setAccessToken(token){access_token=token,console.log("Setting access_token: ",access_token),callback(null,access_token)}if(access_token)return void callback(null,access_token);var options={interactive:!0,url:"https://www.douban.com/service/auth2/auth?client_id="+conf.api_key+"&response_type=code&redirect_uri="+encodeURIComponent(redirectURL)+"&scope="+conf.scopes};chrome.identity.launchWebAuthFlow(options,function(redirectUri){if(console.log("launchWebAuthFlow completed",chrome.runtime.lastError,redirectUri),chrome.runtime.lastError)return void callback(new Error(chrome.runtime.lastError));var matches=redirectUri.match(redirectRe);matches&&matches.length>1?handleProviderResponse(parseRedirectFragment(matches[1])):callback(new Error("Invalid redirect URI"))})}}}();
--------------------------------------------------------------------------------
/public/js/enml.min.js:
--------------------------------------------------------------------------------
1 | var enml=function(){function html2enml(node){var result,$$=angular.element,clone=node.cloneNode(!0),process=$$(''),nodeClassName=$(node).attr("class").split(" ")[0];return $("body").append(process),process=$("#process"),process.append($(clone)),nodewalk(clone.childNodes,process_node),result=$("#process ."+nodeClassName).html().replace(/
/g,"
").replace(/
/g,"
").replace(/
/g,'
'),process.remove(),result}function nodewalk(node,fn){for(var i=0;ii;i++)rule=rules[i],str_style+=rule+":"+getComputedStyle(node).getPropertyValue(rule)+";";node.setAttribute("style",str_style)}}var EXTRACT_RULE={blockquote:["border-left"],span:["color"],code:["border","background-color"],pre:["background-color","margin"]};return{html2enml:html2enml}}();
--------------------------------------------------------------------------------
/public/js/evernote-auth.min.js:
--------------------------------------------------------------------------------
1 | var EvernoteAuth=function(){"use strict";var conf=EvernoteAuthConf,redirectURL="https://"+chrome.runtime.id+".chromiumapp.org/auth/callback",redirectRe=new RegExp(redirectURL+"[#?](.*)"),access_token=null;return{getToken:function(type,callback){function parseRedirectFragment(fragment,options){var pairs=fragment.split(/&/),values={};return pairs.forEach(function(pair){var nameval=pair.split(/=/);values[nameval[0]]=options&&options.decode?decodeURIComponent(nameval[1]):nameval[1]}),values}function handleProviderResponse(values,values2){console.log("providerResponse",values),values.hasOwnProperty("access_token")?setAccessToken(values.access_token):values.hasOwnProperty("oauth_token")?exchangeCodeForToken(values.oauth_token,values2.oauth_verifier):callback(new Error("Neither access_token nor code avialable."))}function exchangeCodeForToken(token,verifier){var xhr=new XMLHttpRequest;xhr.open("POST",conf.hostname[type]+"/oauth?oauth_consumer_key="+conf.consumer_key+"&oauth_signature="+conf.consumer_secret+"%26&oauth_signature_method=PLAINTEXT&oauth_timestamp="+(new Date).valueOf()+"&oauth_nonce="+(new Date).valueOf()+"&oauth_token="+token+"&oauth_verifier="+verifier),xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),xhr.onload=function(){if(200===this.status){var response=parseRedirectFragment(this.responseText,{decode:!0});console.log(response),response.hasOwnProperty("oauth_token")?callback(null,response):callback(new Error("Cannot obtain access_token from code."))}else console.log("code exchange status:",this.status),callback(new Error("Code exchange failed"))},xhr.send()}if(access_token)return void callback(null,access_token);var options={interactive:!0,url:conf.hostname[type]+"/oauth?oauth_consumer_key="+conf.consumer_key+"&oauth_signature="+conf.consumer_secret+"%26&oauth_signature_method=PLAINTEXT&oauth_timestamp="+(new Date).valueOf()+"&oauth_nonce"+(new Date).valueOf()+"&oauth_callback="+encodeURIComponent(redirectURL)},xhr=new XMLHttpRequest;xhr.open("GET",options.url),xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),xhr.onload=function(){if(200===this.status){var resp=parseRedirectFragment(this.responseText);if(console.log(resp),!resp.oauth_callback_confirmed)return;chrome.identity.launchWebAuthFlow({interactive:!0,url:conf.hostname[type]+"/OAuth.action?oauth_token="+resp.oauth_token},function(redirectUri){if(console.log("launchWebAuthFlow completed",chrome.runtime.lastError,redirectUri),chrome.runtime.lastError)return void callback(new Error(chrome.runtime.lastError));var matches=redirectUri.match(redirectRe);matches&&matches.length>1?handleProviderResponse(resp,parseRedirectFragment(redirectUri)):callback(new Error("Invalid redirect URI"))})}},xhr.send()}}}();
--------------------------------------------------------------------------------
/public/js/filters.min.js:
--------------------------------------------------------------------------------
1 | "use strict";angular.module("ANNO").filter("characters",function(){return function(input,chars){if(input&&input.length>=chars){input=input.substring(0,chars);var lastspace=input.lastIndexOf(" ");return-1!==lastspace&&(input=input.substr(0,lastspace)),input+"..."}return input}});
--------------------------------------------------------------------------------
/public/js/ui-directives.min.js:
--------------------------------------------------------------------------------
1 | "use strict";angular.module("ANNO.ui-directives",[]).directive("scrollHide",[function(){return function(scope,elem,attrs){function onScroll(){elem.toggleClass("hide",target.scrollTop>hideFrom)}var hideFrom=attrs.hideFrom||10,target=window;attrs.targetSel&&(target=document.querySelector(attrs.targetSel)),attrs.hideFrom&&scope.$watch(attrs.hideFrom,function(value){hideFrom=parseInt(value,10)}),scope.hide=!1,angular.element(target).on("scroll",_.throttle(onScroll,200))}}]);
--------------------------------------------------------------------------------
/public/lib/angular/angular-cookies.min.js:
--------------------------------------------------------------------------------
1 | !function(window,angular,undefined){"use strict";angular.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function($rootScope,$browser){function push(){var name,value,browserCookies,updated;for(name in lastCookies)isUndefined(cookies[name])&&$browser.cookies(name,undefined);for(name in cookies)value=cookies[name],angular.isString(value)?value!==lastCookies[name]&&($browser.cookies(name,value),updated=!0):angular.isDefined(lastCookies[name])?cookies[name]=lastCookies[name]:delete cookies[name];if(updated){updated=!1,browserCookies=$browser.cookies();for(name in cookies)cookies[name]!==browserCookies[name]&&(isUndefined(browserCookies[name])?delete cookies[name]:cookies[name]=browserCookies[name],updated=!0)}}var lastBrowserCookies,cookies={},lastCookies={},runEval=!1,copy=angular.copy,isUndefined=angular.isUndefined;return $browser.addPollFn(function(){var currentCookies=$browser.cookies();lastBrowserCookies!=currentCookies&&(lastBrowserCookies=currentCookies,copy(currentCookies,lastCookies),copy(currentCookies,cookies),runEval&&$rootScope.$apply())})(),runEval=!0,$rootScope.$watch(push),cookies}]).factory("$cookieStore",["$cookies",function($cookies){return{get:function(key){var value=$cookies[key];return value?angular.fromJson(value):value},put:function(key,value){$cookies[key]=angular.toJson(value)},remove:function(key){delete $cookies[key]}}}])}(window,window.angular);
--------------------------------------------------------------------------------
/public/lib/angular/angular-resource.min.js:
--------------------------------------------------------------------------------
1 | !function(window,angular,undefined){"use strict";function isValidDottedPath(path){return null!=path&&""!==path&&"hasOwnProperty"!==path&&MEMBER_NAME_REGEX.test("."+path)}function lookupDottedPath(obj,path){if(!isValidDottedPath(path))throw $resourceMinErr("badmember",'Dotted member path "@{0}" is invalid.',path);for(var keys=path.split("."),i=0,ii=keys.length;ii>i&&obj!==undefined;i++){var key=keys[i];obj=null!==obj?obj[key]:undefined}return obj}function shallowClearAndCopy(src,dst){dst=dst||{},angular.forEach(dst,function(value,key){delete dst[key]});for(var key in src)!src.hasOwnProperty(key)||"$"===key.charAt(0)&&"$"===key.charAt(1)||(dst[key]=src[key]);return dst}var $resourceMinErr=angular.$$minErr("$resource"),MEMBER_NAME_REGEX=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;angular.module("ngResource",["ng"]).factory("$resource",["$http","$q",function($http,$q){function encodeUriSegment(val){return encodeUriQuery(val,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function encodeUriQuery(val,pctEncodeSpaces){return encodeURIComponent(val).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,pctEncodeSpaces?"%20":"+")}function Route(template,defaults){this.template=template,this.defaults=defaults||{},this.urlParams={}}function resourceFactory(url,paramDefaults,actions){function extractParams(data,actionParams){var ids={};return actionParams=extend({},paramDefaults,actionParams),forEach(actionParams,function(value,key){isFunction(value)&&(value=value()),ids[key]=value&&value.charAt&&"@"==value.charAt(0)?lookupDottedPath(data,value.substr(1)):value}),ids}function defaultResponseInterceptor(response){return response.resource}function Resource(value){shallowClearAndCopy(value||{},this)}var route=new Route(url);return actions=extend({},DEFAULT_ACTIONS,actions),forEach(actions,function(action,name){var hasBody=/^(POST|PUT|PATCH)$/i.test(action.method);Resource[name]=function(a1,a2,a3,a4){var data,success,error,params={};switch(arguments.length){case 4:error=a4,success=a3;case 3:case 2:if(!isFunction(a2)){params=a1,data=a2,success=a3;break}if(isFunction(a1)){success=a1,error=a2;break}success=a2,error=a3;case 1:isFunction(a1)?success=a1:hasBody?data=a1:params=a1;break;case 0:break;default:throw $resourceMinErr("badargs","Expected up to 4 arguments [params, data, success, error], got {0} arguments",arguments.length)}var isInstanceCall=this instanceof Resource,value=isInstanceCall?data:action.isArray?[]:new Resource(data),httpConfig={},responseInterceptor=action.interceptor&&action.interceptor.response||defaultResponseInterceptor,responseErrorInterceptor=action.interceptor&&action.interceptor.responseError||undefined;forEach(action,function(value,key){"params"!=key&&"isArray"!=key&&"interceptor"!=key&&(httpConfig[key]=copy(value))}),hasBody&&(httpConfig.data=data),route.setUrlParams(httpConfig,extend({},extractParams(data,action.params||{}),params),action.url);var promise=$http(httpConfig).then(function(response){var data=response.data,promise=value.$promise;if(data){if(angular.isArray(data)!==!!action.isArray)throw $resourceMinErr("badcfg","Error in resource configuration. Expected response to contain an {0} but got an {1}",action.isArray?"array":"object",angular.isArray(data)?"array":"object");action.isArray?(value.length=0,forEach(data,function(item){value.push(new Resource(item))})):(shallowClearAndCopy(data,value),value.$promise=promise)}return value.$resolved=!0,response.resource=value,response},function(response){return value.$resolved=!0,(error||noop)(response),$q.reject(response)});return promise=promise.then(function(response){var value=responseInterceptor(response);return(success||noop)(value,response.headers),value},responseErrorInterceptor),isInstanceCall?promise:(value.$promise=promise,value.$resolved=!1,value)},Resource.prototype["$"+name]=function(params,success,error){isFunction(params)&&(error=success,success=params,params={});var result=Resource[name].call(this,params,this,success,error);return result.$promise||result}}),Resource.bind=function(additionalParamDefaults){return resourceFactory(url,extend({},paramDefaults,additionalParamDefaults),actions)},Resource}var DEFAULT_ACTIONS={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},noop=angular.noop,forEach=angular.forEach,extend=angular.extend,copy=angular.copy,isFunction=angular.isFunction;return Route.prototype={setUrlParams:function(config,params,actionUrl){var val,encodedVal,self=this,url=actionUrl||self.template,urlParams=self.urlParams={};forEach(url.split(/\W/),function(param){if("hasOwnProperty"===param)throw $resourceMinErr("badname","hasOwnProperty is not a valid parameter name.");!new RegExp("^\\d+$").test(param)&¶m&&new RegExp("(^|[^\\\\]):"+param+"(\\W|$)").test(url)&&(urlParams[param]=!0)}),url=url.replace(/\\:/g,":"),params=params||{},forEach(self.urlParams,function(_,urlParam){val=params.hasOwnProperty(urlParam)?params[urlParam]:self.defaults[urlParam],angular.isDefined(val)&&null!==val?(encodedVal=encodeUriSegment(val),url=url.replace(new RegExp(":"+urlParam+"(\\W|$)","g"),function(match,p1){return encodedVal+p1})):url=url.replace(new RegExp("(/?):"+urlParam+"(\\W|$)","g"),function(match,leadingSlashes,tail){return"/"==tail.charAt(0)?tail:leadingSlashes+tail})}),url=url.replace(/\/+$/,"")||"/",url=url.replace(/\/\.(?=\w+($|\?))/,"."),config.url=url.replace(/\/\\\./,"/."),forEach(params,function(value,key){self.urlParams[key]||(config.params=config.params||{},config.params[key]=value)})}},resourceFactory}])}(window,window.angular);
--------------------------------------------------------------------------------
/public/lib/angular/angular-route.min.js:
--------------------------------------------------------------------------------
1 | !function(window,angular){"use strict";function $RouteProvider(){function inherit(parent,extra){return angular.extend(new(angular.extend(function(){},{prototype:parent})),extra)}function pathRegExp(path,opts){var insensitive=opts.caseInsensitiveMatch,ret={originalPath:path,regexp:path},keys=ret.keys=[];return path=path.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(_,slash,key,option){var optional="?"===option?option:null,star="*"===option?option:null;return keys.push({name:key,optional:!!optional}),slash=slash||"",""+(optional?"":slash)+"(?:"+(optional?slash:"")+(star&&"(.+?)"||"([^/]+)")+(optional||"")+")"+(optional||"")}).replace(/([\/$\*])/g,"\\$1"),ret.regexp=new RegExp("^"+path+"$",insensitive?"i":""),ret}var routes={};this.when=function(path,route){if(routes[path]=angular.extend({reloadOnSearch:!0},route,path&&pathRegExp(path,route)),path){var redirectPath="/"==path[path.length-1]?path.substr(0,path.length-1):path+"/";routes[redirectPath]=angular.extend({redirectTo:path},pathRegExp(redirectPath,route))}return this},this.otherwise=function(params){return this.when(null,params),this},this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function($rootScope,$location,$routeParams,$q,$injector,$http,$templateCache,$sce){function switchRouteMatcher(on,route){var keys=route.keys,params={};if(!route.regexp)return null;var m=route.regexp.exec(on);if(!m)return null;for(var i=1,len=m.length;len>i;++i){var key=keys[i-1],val="string"==typeof m[i]?decodeURIComponent(m[i]):m[i];key&&val&&(params[key.name]=val)}return params}function updateRoute(){var next=parseRoute(),last=$route.current;next&&last&&next.$$route===last.$$route&&angular.equals(next.pathParams,last.pathParams)&&!next.reloadOnSearch&&!forceReload?(last.params=next.params,angular.copy(last.params,$routeParams),$rootScope.$broadcast("$routeUpdate",last)):(next||last)&&(forceReload=!1,$rootScope.$broadcast("$routeChangeStart",next,last),$route.current=next,next&&next.redirectTo&&(angular.isString(next.redirectTo)?$location.path(interpolate(next.redirectTo,next.params)).search(next.params).replace():$location.url(next.redirectTo(next.pathParams,$location.path(),$location.search())).replace()),$q.when(next).then(function(){if(next){var template,templateUrl,locals=angular.extend({},next.resolve);return angular.forEach(locals,function(value,key){locals[key]=angular.isString(value)?$injector.get(value):$injector.invoke(value)}),angular.isDefined(template=next.template)?angular.isFunction(template)&&(template=template(next.params)):angular.isDefined(templateUrl=next.templateUrl)&&(angular.isFunction(templateUrl)&&(templateUrl=templateUrl(next.params)),templateUrl=$sce.getTrustedResourceUrl(templateUrl),angular.isDefined(templateUrl)&&(next.loadedTemplateUrl=templateUrl,template=$http.get(templateUrl,{cache:$templateCache}).then(function(response){return response.data}))),angular.isDefined(template)&&(locals.$template=template),$q.all(locals)}}).then(function(locals){next==$route.current&&(next&&(next.locals=locals,angular.copy(next.params,$routeParams)),$rootScope.$broadcast("$routeChangeSuccess",next,last))},function(error){next==$route.current&&$rootScope.$broadcast("$routeChangeError",next,last,error)}))}function parseRoute(){var params,match;return angular.forEach(routes,function(route){!match&&(params=switchRouteMatcher($location.path(),route))&&(match=inherit(route,{params:angular.extend({},$location.search(),params),pathParams:params}),match.$$route=route)}),match||routes[null]&&inherit(routes[null],{params:{},pathParams:{}})}function interpolate(string,params){var result=[];return angular.forEach((string||"").split(":"),function(segment,i){if(0===i)result.push(segment);else{var segmentMatch=segment.match(/(\w+)(.*)/),key=segmentMatch[1];result.push(params[key]),result.push(segmentMatch[2]||""),delete params[key]}}),result.join("")}var forceReload=!1,$route={routes:routes,reload:function(){forceReload=!0,$rootScope.$evalAsync(updateRoute)}};return $rootScope.$on("$locationChangeSuccess",updateRoute),$route}]}function $RouteParamsProvider(){this.$get=function(){return{}}}function ngViewFactory($route,$anchorScroll,$animate){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(scope,$element,attr,ctrl,$transclude){function cleanupLastView(){previousElement&&(previousElement.remove(),previousElement=null),currentScope&&(currentScope.$destroy(),currentScope=null),currentElement&&($animate.leave(currentElement,function(){previousElement=null}),previousElement=currentElement,currentElement=null)}function update(){var locals=$route.current&&$route.current.locals,template=locals&&locals.$template;if(angular.isDefined(template)){var newScope=scope.$new(),current=$route.current,clone=$transclude(newScope,function(clone){$animate.enter(clone,null,currentElement||$element,function(){!angular.isDefined(autoScrollExp)||autoScrollExp&&!scope.$eval(autoScrollExp)||$anchorScroll()}),cleanupLastView()});currentElement=clone,currentScope=current.scope=newScope,currentScope.$emit("$viewContentLoaded"),currentScope.$eval(onloadExp)}else cleanupLastView()}var currentScope,currentElement,previousElement,autoScrollExp=attr.autoscroll,onloadExp=attr.onload||"";scope.$on("$routeChangeSuccess",update),update()}}}function ngViewFillContentFactory($compile,$controller,$route){return{restrict:"ECA",priority:-400,link:function(scope,$element){var current=$route.current,locals=current.locals;$element.html(locals.$template);var link=$compile($element.contents());if(current.controller){locals.$scope=scope;var controller=$controller(current.controller,locals);current.controllerAs&&(scope[current.controllerAs]=controller),$element.data("$ngControllerController",controller),$element.children().data("$ngControllerController",controller)}link(scope)}}}var ngRouteModule=angular.module("ngRoute",["ng"]).provider("$route",$RouteProvider);ngRouteModule.provider("$routeParams",$RouteParamsProvider),ngRouteModule.directive("ngView",ngViewFactory),ngRouteModule.directive("ngView",ngViewFillContentFactory),ngViewFactory.$inject=["$route","$anchorScroll","$animate"],ngViewFillContentFactory.$inject=["$compile","$controller","$route"]}(window,window.angular);
--------------------------------------------------------------------------------
/public/lib/angular/angular-sanitize.min.js:
--------------------------------------------------------------------------------
1 | !function(window,angular){"use strict";function $SanitizeProvider(){this.$get=["$$sanitizeUri",function($$sanitizeUri){return function(html){var buf=[];return htmlParser(html,htmlSanitizeWriter(buf,function(uri,isImage){return!/^unsafe/.test($$sanitizeUri(uri,isImage))})),buf.join("")}}]}function sanitizeText(chars){var buf=[],writer=htmlSanitizeWriter(buf,angular.noop);return writer.chars(chars),buf.join("")}function makeMap(str){var i,obj={},items=str.split(",");for(i=0;i=0&&stack[pos]!=tagName;pos--);if(pos>=0){for(i=stack.length-1;i>=pos;i--)handler.end&&handler.end(stack[i]);stack.length=pos}}var index,chars,match,stack=[],last=html;for(stack.last=function(){return stack[stack.length-1]};html;){if(chars=!0,stack.last()&&specialElements[stack.last()])html=html.replace(new RegExp("(.*)<\\s*\\/\\s*"+stack.last()+"[^>]*>","i"),function(all,text){return text=text.replace(COMMENT_REGEXP,"$1").replace(CDATA_REGEXP,"$1"),handler.chars&&handler.chars(decodeEntities(text)),""}),parseEndTag("",stack.last());else if(0===html.indexOf("",index)===index&&(handler.comment&&handler.comment(html.substring(4,index)),html=html.substring(index+3),chars=!1)):DOCTYPE_REGEXP.test(html)?(match=html.match(DOCTYPE_REGEXP),match&&(html=html.replace(match[0],""),chars=!1)):BEGING_END_TAGE_REGEXP.test(html)?(match=html.match(END_TAG_REGEXP),match&&(html=html.substring(match[0].length),match[0].replace(END_TAG_REGEXP,parseEndTag),chars=!1)):BEGIN_TAG_REGEXP.test(html)&&(match=html.match(START_TAG_REGEXP),match&&(html=html.substring(match[0].length),match[0].replace(START_TAG_REGEXP,parseStartTag),chars=!1)),chars){index=html.indexOf("<");var text=0>index?html:html.substring(0,index);html=0>index?"":html.substring(index),handler.chars&&handler.chars(decodeEntities(text))}if(html==last)throw $sanitizeMinErr("badparse","The sanitizer was unable to parse the following block of html: {0}",html);last=html}parseEndTag()}function decodeEntities(value){if(!value)return"";var parts=spaceRe.exec(value),spaceBefore=parts[1],spaceAfter=parts[3],content=parts[2];return content&&(hiddenPre.innerHTML=content.replace(//g,">")}function htmlSanitizeWriter(buf,uriValidator){var ignore=!1,out=angular.bind(buf,buf.push);return{start:function(tag,attrs,unary){tag=angular.lowercase(tag),!ignore&&specialElements[tag]&&(ignore=tag),ignore||validElements[tag]!==!0||(out("<"),out(tag),angular.forEach(attrs,function(value,key){var lkey=angular.lowercase(key),isImage="img"===tag&&"src"===lkey||"background"===lkey;validAttrs[lkey]!==!0||uriAttrs[lkey]===!0&&!uriValidator(value,isImage)||(out(" "),out(key),out('="'),out(encodeEntities(value)),out('"'))}),out(unary?"/>":">"))},end:function(tag){tag=angular.lowercase(tag),ignore||validElements[tag]!==!0||(out(""),out(tag),out(">")),tag==ignore&&(ignore=!1)},chars:function(chars){ignore||out(encodeEntities(chars))}}}var $sanitizeMinErr=angular.$$minErr("$sanitize"),START_TAG_REGEXP=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,END_TAG_REGEXP=/^<\s*\/\s*([\w:-]+)[^>]*>/,ATTR_REGEXP=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,BEGIN_TAG_REGEXP=/^,BEGING_END_TAGE_REGEXP=/^<\s*\//,COMMENT_REGEXP=//g,DOCTYPE_REGEXP=/]*?)>/i,CDATA_REGEXP=//g,NON_ALPHANUMERIC_REGEXP=/([^\#-~| |!])/g,voidElements=makeMap("area,br,col,hr,img,wbr"),optionalEndTagBlockElements=makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),optionalEndTagInlineElements=makeMap("rp,rt"),optionalEndTagElements=angular.extend({},optionalEndTagInlineElements,optionalEndTagBlockElements),blockElements=angular.extend({},optionalEndTagBlockElements,makeMap("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),inlineElements=angular.extend({},optionalEndTagInlineElements,makeMap("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),specialElements=makeMap("script,style"),validElements=angular.extend({},voidElements,blockElements,inlineElements,optionalEndTagElements),uriAttrs=makeMap("background,cite,href,longdesc,src,usemap"),validAttrs=angular.extend({},uriAttrs,makeMap("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width")),hiddenPre=document.createElement("pre"),spaceRe=/^(\s*)([\s\S]*?)(\s*)$/;angular.module("ngSanitize",[]).provider("$sanitize",$SanitizeProvider),angular.module("ngSanitize").filter("linky",["$sanitize",function($sanitize){var LINKY_URL_REGEXP=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,MAILTO_REGEXP=/^mailto:/;return function(text,target){function addText(text){text&&html.push(sanitizeText(text))}function addLink(url,text){html.push("'),addText(text),html.push("")}if(!text)return text;for(var match,url,i,raw=text,html=[];match=raw.match(LINKY_URL_REGEXP);)url=match[0],match[2]==match[3]&&(url="mailto:"+url),i=match.index,addText(raw.substr(0,i)),addLink(url,match[0].replace(MAILTO_REGEXP,"")),raw=raw.substring(i+match[0].length);return addText(raw),$sanitize(html.join(""))}}])}(window,window.angular);
--------------------------------------------------------------------------------
/public/lib/codemirror/codemirror.min.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NdYAG/ANNO/8bdde81ec10e63a2814fa1cab700986df751e53e/public/lib/codemirror/codemirror.min.js
--------------------------------------------------------------------------------
/public/lib/ng-infinite-scroll/ng-infinite-scroll.min.js:
--------------------------------------------------------------------------------
1 | var mod;mod=angular.module("infinite-scroll",[]),mod.directive("infiniteScroll",["$rootScope","$timeout",function($rootScope,$timeout){return{link:function(scope,elem,attrs){var checkWhenEnabled,handler,scrollDistance,scrollEnabled,scrollElemSel=attrs.scrollElemSel||"body",scrollElem=document.querySelector(scrollElemSel),$scrollElem=angular.element(scrollElem);return scrollDistance=0,null!=attrs.infiniteScrollDistance&&scope.$watch(attrs.infiniteScrollDistance,function(value){return scrollDistance=parseInt(value,10)}),scrollEnabled=!0,checkWhenEnabled=!1,null!=attrs.infiniteScrollDisabled&&scope.$watch(attrs.infiniteScrollDisabled,function(value){return scrollEnabled=!value,scrollEnabled&&checkWhenEnabled?(checkWhenEnabled=!1,handler()):void 0}),handler=function(){var elementBottom,remaining,shouldScroll,windowBottom;return windowBottom=scrollElem.clientHeight+scrollElem.scrollTop,elementBottom=elem[0].offsetTop+elem[0].clientHeight,remaining=elementBottom-windowBottom,shouldScroll=remaining<=scrollElem.clientHeight*scrollDistance,shouldScroll&&scrollEnabled?$rootScope.$$phase?scope.$eval(attrs.infiniteScroll):scope.$apply(attrs.infiniteScroll):shouldScroll?checkWhenEnabled=!0:void 0},$scrollElem.on("scroll",_.throttle(handler,200)),scope.$on("$destroy",function(){return $scrollElem.off("scroll",handler)}),$timeout(function(){return attrs.infiniteScrollImmediateCheck?scope.$eval(attrs.infiniteScrollImmediateCheck)?handler():void 0:handler()},0)}}}]);
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "读书笔记",
3 | "description": "豆瓣读书笔记第三方应用",
4 | "version": "0.2.5",
5 | "manifest_version": 2,
6 | "minimum_chrome_version": "29",
7 | "app": {
8 | "background": {
9 | "scripts": ["background.js"]
10 | }
11 | },
12 | "icons": { "16": "img/books.png", "128": "img/books.png" },
13 | "permissions": [
14 | {"fileSystem": ["write"]}
15 | , "storage"
16 | , "identity"
17 | , "https://www.douban.com/*", "https://api.douban.com/*", "http://*.douban.com/*"
18 | , "https://*.evernote.com/*", "http://*.evernote.com/*"
19 | , "https://*.yinxiang.com/*", "http://*.yinxiang.com/*"
20 | , "https://www.google-analytics.com/"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/public/partials/about.html:
--------------------------------------------------------------------------------
1 | 读书笔记
读书笔记是基于豆瓣读书笔记开放接口开发的应用
2 | 支持 Github Flavor Markdown 书写笔记
3 | 如果遇到 BUG、体验不好的地方,或者希望增加功能
4 | 欢迎通过以下方式联系反馈
5 |
反馈
已知问题
- 第三方应用无法读取私密笔记的图片。建议设置带图笔记权限为公开。
- 豆瓣读书尚未开放收藏接口,因此收藏的内容存储在Chrome云端。在豆瓣开放接口后,应用将更新,统一你的收藏数据。
使用的开源项目
--------------------------------------------------------------------------------
/public/partials/book.html:
--------------------------------------------------------------------------------
1 | {{ book.summary }}
去豆瓣看这本书{{ book.title }}
--------------------------------------------------------------------------------
/public/partials/books.html:
--------------------------------------------------------------------------------
1 | {{ book.notes_count }}篇笔记
写新笔记 {{ book.title }}{{ book.author[0] }}
还没有写过笔记
--------------------------------------------------------------------------------
/public/partials/edit.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/partials/error.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/partials/fav.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/partials/friends.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/partials/info.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/partials/login.html:
--------------------------------------------------------------------------------
1 | 登录

--------------------------------------------------------------------------------
/public/partials/note.html:
--------------------------------------------------------------------------------
1 | {{ note.chapter || ("第" + note.page_no + "页") }}
--------------------------------------------------------------------------------
/public/partials/widgets/editor.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/partials/widgets/reader.html:
--------------------------------------------------------------------------------
1 | {{ article.book.title }}:
{{ article.chapter }}
--------------------------------------------------------------------------------
/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | header.header(ng-class="{ 'pushed' : asideVisible, 'vanished' : headerInvisible }")
5 | span.icon-sidebar(ng-click="toggleSidebar()")
6 | //-span.title(ng-bind-html="globalTitle")
7 | span.title(compile="globalTitle")
8 | aside.hidden(ng-class="{ 'show': asideVisible }")
9 | div.banner(ng-show="user")
10 | a.avatar(ng-show="user", remote-image="user.avatar", link="/", ng-click="toggleSidebar()")
11 | ul(ng-show="user")
12 | li
13 | a(link="/", ng-click="toggleSidebar()") 我的书架
14 | li(ng-show="lastBook && user")
15 | a(link="/{{ author.uid }}/book/{{ lastBook.id }}") 返回 {{ lastBook.title }}
16 | li
17 | a(link="/{{ user.uid }}/info") 个人
18 | li
19 | a(link="/friends") 友邻笔记
20 | li
21 | a(link="/fav") 我的收藏
22 | li
23 | a(ng-click="logout()") 注销
24 | ul(ng-hide="user")
25 | li
26 | a(link="/login", ng-click="toggleSidebar()") 登录
27 | ul.info
28 | li
29 | a(link="/about") 关于 · 反馈
30 | div.ng-view(ng-class="{ 'pushed' : asideVisible }")
31 | div.loading(loader)
32 | div.rect1
33 | div.rect2
34 | div.rect3
35 | div.rect4
36 | div.rect5
37 | div.alert(alert)
38 | script(type="text/ng-template", id="modalLogout.html")
39 | div.modal-header
40 | h3 确定注销?下次登陆将需要重新前往豆瓣授权。
41 | div.modal-footer.buttons-group
42 | button.button-default(ng-click="cancel()") 取消
43 | button.button-warning(ng-click="ok()") 注销
44 | script(type="text/ng-template", id="modalEvernote.html")
45 | div.modal-evernote
46 | div.modal-header
47 | h3 保存到Evernote/印象笔记
48 | div.modal-body
49 | div.row
50 | label
51 | input(type="radio", ng-model="choice_notebook", value="exist")
52 | 保存已有笔记本
53 | select(ng-show="choice_notebook == 'exist'", ng-model="selectedNotebook", ng-options="nb.name for nb in notebooks")
54 | div.row
55 | label
56 | input(type="radio", ng-model="choice_notebook", value="new")
57 | 创建新笔记本
58 | input(ng-show="choice_notebook == 'new'", type="text", placeholder="输入新笔记本名", ng-model="new_notebook")
59 | div.modal-footer
60 | div.status {{ status }}
61 | div.buttons-group
62 | button.button-default(ng-click="cancel()") 取消
63 | button.button-primary(ng-click="ok(choice_notebook, selectedNotebook, new_notebook)") 保存
64 |
65 | block scripts
66 | script(src="lib/underscore/underscore.min.js")
67 | script(src="lib/angular/angular.min.js")
68 | script(src="lib/angular/angular-route.min.js")
69 | //- script(src="lib/angular/angular-sanitize.min.js")
70 | //- script(src="lib/angular/angular-cookies.min.js")
71 | //- script(src="lib/angular/angular-resource.min.js")
72 | //- script(src="/lib/underscore/underscore.js")
73 |
74 | //- script(src="/lib/angular/angular.js")
75 | //- script(src="/lib/angular/angular-route.js")
76 | //- script(src="/lib/angular/angular-cookies.js")
77 |
78 | script(src="/lib/angular-bootstrap/ui-bootstrap.min.js")
79 | script(src="/lib/angular-bootstrap/ui-bootstrap-tpls.min.js")
80 |
81 | script(src="lib/ng-infinite-scroll/ng-infinite-scroll.min.js")
82 |
83 | script(src="/lib/codemirror/codemirror.min.js")
84 | script(src="/lib/marked/marked.min.js")
85 | script(src="/lib/color-thief/color-thief.min.js")
86 | script(src="/lib/evernote/evernote-sdk.min.js")
87 | script(src="/lib/analytics/ga.bundle.js")
88 |
89 | script(src="/js/config.min.js")
90 | script(src="/js/douban-auth.min.js")
91 | script(src="/js/evernote-auth.min.js")
92 | script(src="/js/app.min.js")
93 | script(src="/js/enml.min.js")
94 | script(src="/js/controllers.min.js")
95 | script(src="/js/services.min.js")
96 | script(src="/js/directives.min.js")
97 | script(src="/js/ui-directives.min.js")
98 | script(src="/js/filters.min.js")
99 |
--------------------------------------------------------------------------------
/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype
2 | html(ng-app="ANNO", ng-controller="appFrameCtrl", ng-class="globalCSSClass", ng-csp)
3 | meta(charset="utf-8")
4 | head
5 | title 读书笔记
6 | link(rel='stylesheet', href='/css/style.css')
7 | link(rel='stylesheet', href='/css/codemirror.css')
8 | link(rel='stylesheet', href='/css/themes/tomorrow.css')
9 | body
10 | block content
11 | block scripts
--------------------------------------------------------------------------------
/views/partials/about.jade:
--------------------------------------------------------------------------------
1 | section#about
2 | h1 读书笔记
3 | p.
4 | 读书笔记是基于豆瓣读书笔记开放接口开发的应用
5 | 支持 Github Flavor Markdown 书写笔记
6 | 如果遇到 BUG、体验不好的地方,或者希望增加功能
7 | 欢迎通过以下方式联系反馈
8 |
9 | h2 反馈
10 | div 阿弟:
11 | a(href="http://www.douban.com/people/sensitive", target="_blank") 豆瓣
12 | a(href="mailto:simonday@foxmail.com", target="_blank") 邮件
13 |
14 | h2 已知问题
15 | ul
16 | li 第三方应用无法读取私密笔记的图片。建议设置带图笔记权限为公开。
17 | li 豆瓣读书尚未开放收藏接口,因此收藏的内容存储在Chrome云端。在豆瓣开放接口后,应用将更新,统一你的收藏数据。
18 |
19 | h2 使用的开源项目
20 | ul
21 | li
22 | a(href="http://angularjs.org", target="_blank") AngularJS
23 | li
24 | a(href="http://codemirror.net", target="_blank") CodeMirror
25 | li
26 | a(href="https://github.com/chjj/marked", target="_blank") Marked
27 | li
28 | a(href="http://lokeshdhakar.com/projects/color-thief/", target="_blank") Color-thief
--------------------------------------------------------------------------------
/views/partials/book.jade:
--------------------------------------------------------------------------------
1 | section#one-book
2 | //- h1
3 | //- a(link="/{{ author.uid }}", ng-show="!author.is_self") < {{ author.name }}的书架
4 | //- {{ book.title }}
5 | div.cubic-view
6 | div.book-cube(color-thief, image="book_cover", ng-model="dominateColor", ng-class="{ 'sided': book_sided }", ng-click="openBook()", ng-mouseleave="closeBook()")
7 | div.book-front(ng-class="{ 'opened': book_opened }")
8 | .book-cover-backface(ng-style="{ 'background-color': dominateColor.background }")
9 | .book-cover-frontface(remote-image="book_cover")
10 | div.book-page
11 | div.page-content
12 | //- a.icon-feather(link="/{{ user.uid }}/book/{{ book.id }}/new")写新笔记
13 | //- a(ng-click="exportNotes()")导出所有笔记
14 | p.summary {{ book.summary }}
15 | a(href="http://book.douban.com/subject/{{ book.id }}/", target="_blank")去豆瓣看这本书
16 | div.book-back(ng-style="{ 'background-color': dominateColor.background }")
17 | div.book-top
18 | div.book-bottom
19 | div.book-left(ng-style="{ 'background-color': dominateColor.background, 'color': dominateColor.foreground }")
20 | span.book-title {{ book.title }}
21 | div.book-right
22 | section.main
23 | // p {{ book.summary }}
24 | header
25 | .right-actions
26 | span 共{{ book.notes_count }}篇笔记
27 | button.button-default.icon-export(ng-click="exportNotes()") 导出
28 | nav.order.button-group()
29 | button.button-plain(ng-click="order='time'; reverse=true", ng-class="{'active': (order=='time')}") 最新
30 | button.button-plain(ng-click="order='page_no'; reverse=false", ng-class="{'active': (order=='page_no')}") 页码
31 | a.button-primary.icon-feather(link="/{{ user.uid }}/book/{{ book.id }}/new")
32 | 写新笔记
33 | ul.notes
34 | li.note(ng-repeat="note in book.notes | orderBy: order : reverse")
35 | a.note-mask(link="/note/{{ note.id }}")
36 | time(datetime="{{ note.time }}") {{ note.time | date: note.time : 'longDate'}}
37 | h3 {{ note.chapter || ("第" + note.page_no + "页") }}
38 | p.note-summary {{ note.summary }}
39 | span.note-page(ng-class="{ 'icon-lock': note.privacy == '1' }") P{{ note.page_no }}
40 |
41 | script(type="text/ng-template", id="modalExport.html")
42 | div.modal-export
43 | div.modal-header
44 | h3 选择导出格式
45 | div.modal-body
46 | div.buttons-group
47 | button.button-radio(ng-click="changeExportFormat('markdown')", ng-class="{ 'active': exportFormat == 'markdown' }") Markdown
48 | button.button-radio(ng-click="changeExportFormat('html')", ng-class="{ 'active': exportFormat == 'html' }") HTML
49 | div.export-preview
50 | div.preview-markdown(ng-show="exportFormat == 'markdown'") {{ exports }}
51 | div.preview-html(ng-show="exportFormat == 'html'")
52 | previewer(markdown="exports")
53 | div.modal-footer
54 | div
55 | button.icon-evernote(ng-show="user.evernote || user.yinxiang", ng-click="popupEvernote()") 保存到Evernote
56 | div.buttons-group
57 | button.button-default(ng-click="cancel()") 取消
58 | button.button-primary(ng-click="download()") 导出
59 |
--------------------------------------------------------------------------------
/views/partials/books.jade:
--------------------------------------------------------------------------------
1 | section#all-books
2 | //- h1(ng-show="user.is_self") 所有书
3 | //- h1(ng-show="!user.is_self") {{ user.name }}的书架
4 | ul.books
5 | li.book(ng-repeat="book in books")
6 | div.cover(remote-image='book.images.large')
7 | a.book-link(link="/{{ user.uid }}/book/{{ book.id }}")
8 | div.notes-count
9 | span.count {{ book.notes_count }}
10 | span.count-text 篇笔记
11 | a.add-note(ng-show='user.is_self', link="/{{ user.uid }}/book/{{ book.id }}/new")
12 | i.icon-plus
13 | span 写新笔记
14 | span.title {{ book.title }}
15 | span.author {{ book.author[0] }}
16 | li.book.empty(ng-show="user.is_self", ng-click="searchBook()")
17 | div.cover
18 | p(ng-show="tipVisible") 还没有写过笔记
19 |
20 | script(type="text/ng-template", id="modalSearchBook.html")
21 | div.modal-search
22 | div.modal-header
23 | h3 给一本书写笔记
24 | div.modal-body
25 | input#new-book-name(type="text", placeholder="输入书名,回车查找", input-enter="searchBook(query)", ng-model="query")
26 | input#new-book-id(type="hidden", value="{{ selectedBook.id }}")
27 | ul.search-results
28 | li.result(ng-repeat="book in books", ng-click="selectBook($index, book)", ng-class="{'active': $index == selectedIndex}")
29 | img(remote-image="book.images.large")
30 | div.result-info
31 | h4 {{ book.title }}
32 | p {{ book.summary | characters: 120 }}
33 | div.tip(ng-show="has_searched") 搜索不到? 直接输入想读的书对应的豆瓣Subject ID (下例红色部分)
34 | div.tip(ng-show="has_searched")
35 | | http://book.douban.com/subject/25746578/
36 | div.modal-footer
37 | div.selected-bookname {{ selectedBook.title }}
38 | div.buttons-group
39 | button.button-default(ng-click="cancel()") 取消
40 | button.button-primary(ng-click="ok(selectedBook.id)", ng-disabled="!selectedBook") 开始写
41 |
--------------------------------------------------------------------------------
/views/partials/edit.jade:
--------------------------------------------------------------------------------
1 | ul#navigation
2 | li
3 | a.js-edit.icon-feather(data-title="编辑", ng-click="changeMode('edit-mode')", ng-class="{ 'active': mode == 'edit-mode' }")
4 | li
5 | a.js-preview.icon-eye(data-title="预览", ng-click="changeMode('read-mode')", ng-class="{ 'active': mode == 'read-mode' }")
6 | li
7 | a.js-columns.icon-columns(data-title="编辑+预览", ng-click="changeMode('column-mode')", ng-class="{ 'active': mode == 'column-mode' }")
8 | li
9 | a.js-fullscreen(data-title="全屏", ng-click="fullscreen()", ng-class="{ 'icon-restore': is_fullscreen, 'icon-fullscreen': !is_fullscreen }")
10 | li
11 | a.js-save.icon-save(data-title="保存", data-ajax="{{ action }}", ng-click="save()")
12 |
13 |
14 | div.container(ng-class="mode")
15 | editor(article="note", images="images")
16 | reader(article="note", images="images")
--------------------------------------------------------------------------------
/views/partials/error.jade:
--------------------------------------------------------------------------------
1 | section#error
2 | div.icon-sad.not_found 哎哟,发生了错误
3 | a.button-warning(link="/") 返回书架
--------------------------------------------------------------------------------
/views/partials/fav.jade:
--------------------------------------------------------------------------------
1 | section#fav
2 | //-h1 我收藏的笔记
3 | //- sup *
4 | ul.notes
5 | li.note(ng-repeat="note in notes | orderBy: order : reverse")
6 | a.note-mask(link="/note/{{ note.id }}")
7 | time(datetime="{{ note.time }}") {{ note.time | date: note.time : 'longDate'}}
8 | h3 {{ note.chapter || ("第" + note.page_no + "页") }}
9 | p.note-summary {{ note.summary }}
10 | span.note-page(ng-class="{ 'icon-lock': note.privacy == '1' }") P{{ note.page_no }}
11 | div.footnote
12 | small *收藏的笔记存储在Chrome云端,并未与豆瓣收藏同步。
13 | a(link='/about') 为什么?
--------------------------------------------------------------------------------
/views/partials/friends.jade:
--------------------------------------------------------------------------------
1 | section#friends
2 | //- h1 友邻的笔记
3 | ul.friends(infinite-scroll="showMoreFriends()", scroll-elem-sel=".ng-view")
4 | li.friend(ng-repeat="friend in friends")
5 | a(link="/{{ friend.uid }}")
6 | img(remote-image="friend.small_avatar")
7 | span {{ friend.screen_name }}
8 | li.friend.more
9 | a.icon-more(ng-click="showMoreFriends()", ng-show="and_more")
10 |
--------------------------------------------------------------------------------
/views/partials/info.jade:
--------------------------------------------------------------------------------
1 | section#info
2 | section.statistic
3 | h1 {{ user.name }}
4 | div.summary.ui-card.
5 | 从{{ start }}到{{ end }}我一共给{{ total_books }}本书写了{{ total_notes }}篇笔记
6 | 第一篇笔记{{ start_note.chapter }}来自{{ start_book.title }}
7 | 其中给{{ max_book.title }}写了最多笔记,一共{{ max_book.notes_count }}篇
8 | section.account
9 | h1 绑定Evernote
10 | div.ui-card.bind-choice(ng-show="(!evernote_bound) && (!yinxiang_bound)")
11 | span.icon-evernote
12 | a(ng-click=" authEvernote('evernote') ")
13 | span Evernote International
14 | a(ng-click=" authEvernote('yinxiang') ")
15 | span 印象笔记
16 | div.ui-card.unbind(ng-show="evernote_bound", ng-click=" unbind('evernote') ")
17 | a.icon-evernote.active
18 | span Evernote International
19 | div.ui-card.unbind(ng-show="yinxiang_bound", ng-click=" unbind('yinxiang') ")
20 | a.icon-evernote.active(ng-show="yinxiang_bound")
21 | span 印象笔记
22 | section.recent
23 | h1 最近写过笔记的书
24 | ul.books
25 | li.book(ng-repeat="book in recent_books")
26 | div.cover(remote-image="book.images.large")
27 | a.book-link(link="/{{ user.uid }}/book/{{ book.id }}")
28 | a.add-note.icon-pen(link="/{{ user.uid }}/book/{{ book.id }}/new", title="继续写")
29 | section.analytics
30 | h1 发送错误报告
31 | div.options.ui-card
32 | label
33 | input(type="checkbox", ng-model="analytics_allowed", ng-change=" alter_analytics() ")
34 | | 允许发生错误时发送匿名数据报告
35 | div.tip * 帮助读书笔记变得更健壮,报告完全匿名,不带有你的任何用户信息
36 |
--------------------------------------------------------------------------------
/views/partials/login.jade:
--------------------------------------------------------------------------------
1 | div.auth
2 | h1= "登录"
3 | p
4 | a(title="用豆瓣帐号登录", ng-click="getToken()")
5 | img(alt="登录", src="/img/login_with_douban_32.png")
--------------------------------------------------------------------------------
/views/partials/note.jade:
--------------------------------------------------------------------------------
1 | section#one-note(ng-hide="error")
2 | h1 {{ note.chapter || ("第" + note.page_no + "页") }}
3 | div.meta
4 | a(link="/{{ author.uid }}/book/{{ book.id }}") {{ author.name }}《{{ book.title }}》的笔记
5 | time(datetime="{{ note.time }}") {{ note.time | date: note.time : 'longDate'}}
6 | span.note-privacy(ng-class="{'icon-lock': note.privacy === 1}")
7 | div.operate(scroll-hide, hide-from=60, target-sel=".ng-view")
8 | a.icon-edit(ng-show="can_operate", link="/note/{{ note.id }}/edit")
9 | span 编辑
10 | a.icon-star-empty(ng-show="!has_starred", ng-click="toggleStar(true)")
11 | span 收藏
12 | a.icon-star(ng-show="has_starred", ng-click="toggleStar(false)")
13 | span 已收藏
14 | a.icon-evernote(ng-show="user.evernote || user.yinxiang", ng-click="popupEvernote()") 保存到Evernote
15 | a.icon-delete(ng-show="can_operate", ng-click="open()")
16 | span 删除
17 | previewer(markdown="note.content")
18 | //- div.share.typo-center
19 | //- a(href="javascript:void(function(){var d=document,url='http://book.douban.com/annotation/',splits=d.location.href.split('/'),nid=splits[splits.length-1],title=document.querySelector('.meta a').textContent,e=encodeURIComponent,s1=window.getSelection,s2=d.getSelection,s3=d.selection,s=s1?s1():s2?s2():s3?s3.createRange().text:'',r='http://www.douban.com/recommend/?url='+e(url+nid)+'&title='+e(title)+'&sel='+e(s)+'&v=1',w=450,h=330,x=function(){if(!window.open(r,'douban','toolbar=0,resizable=1,scrollbars=yes,status=1,width='+w+',height='+h+',left='+(screen.width-w)/2+',top='+(screen.height-h)/2))location.href=r+'&r=1'};if(/Firefox/.test(navigator.userAgent)){setTimeout(x,0)}else{x()}})()")
20 | //- img(src="http://img2.douban.com/pics/fw2douban1.png")
21 |
22 | script(type="text/ng-template", id="modalDeleteNote.html")
23 | div.modal-header
24 | h3 确定删除这篇笔记?
25 | div.modal-footer.buttons-group
26 | button.button-default(ng-click="cancel()") 取消
27 | button.button-warning(ng-click="ok()") 删除
28 |
--------------------------------------------------------------------------------
/views/partials/widgets/editor.jade:
--------------------------------------------------------------------------------
1 | div#editor
2 | form(name="editorForm", novalidate)
3 | span.book-title(ng-show="article.book") {{ article.book.title }}:
4 | input#title(type="text", placeholder="笔记标题", ng-model="article.chapter")
5 | span#page
6 | label 页码:
7 | input#page_no(type="number", name="page_no", placeholder="填写 页码 或 章节最后一页", ng-model="article.page_no", min="0", max="50000")
8 | span#privacy
9 | label
10 | input(type="radio", name="privacy", value="public", ng-model="article.privacy")
11 | 公开
12 | label
13 | input(type="radio", name="privacy", value="private", ng-model="article.privacy")
14 | 隐藏
15 | ul.toolbar
16 | li.icon-bold(ng-click="bold()")
17 | li.icon-italic(ng-click="italic()")
18 | li.icon-image.js-insert-image
19 | input#upload(type="file", multiple, filereader)
20 | input#last_photo(type="hidden", value="{{ article.last_photo || 0 }}")
21 | li.icon-left-quote(ng-click="quote()")
22 | // li.icon-link
23 | li.icon-help(ng-click="toggleEditorHelp()")
24 | codemirror(ng-model="article.content", onload="codeMirrorLoad")
25 | script(type="text/ng-template", id="modalEditorHelp.html")
26 | div.modal-help
27 | div.modal-header
28 | h3 用 Markdown 写笔记
29 | div.modal-body.
30 | Markdown
31 | Markdown 的语法很简单,可以让你快速地写下内容,不用拘泥于格式。
32 | 这里有一张 Github Flavor Markdown 的 cheat sheet,帮助你快速熟悉语法。 https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
33 | 编辑器的工具栏上目前有加粗、倾斜、图片、原文,可以简单上手。
34 | 编辑模式与日记保存
35 | 在右边导航栏上,可以切换编辑、预览、即时编辑模式,以及切换全屏和保存。
36 | div.modal-footer
37 | button.button-primary(ng-click="ok()") 知道啦
38 |
--------------------------------------------------------------------------------
/views/partials/widgets/reader.jade:
--------------------------------------------------------------------------------
1 | div#reader
2 | div.book-title.typo-center(ng-show="article.book") {{ article.book.title }}:
3 | h1.title {{ article.chapter }}
4 | previewer(markdown="article.content", images="images")
--------------------------------------------------------------------------------