songbooks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +29 -0
  7. data/LICENSE +22 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +41 -0
  10. data/Rakefile +6 -0
  11. data/app/assets/javascripts/application.coffee +4 -0
  12. data/app/assets/javascripts/components/filters/convertWhitespace.filter.coffee +3 -0
  13. data/app/assets/javascripts/components/song_component/chord.directive.coffee +9 -0
  14. data/app/assets/javascripts/components/song_component/literal.directive.coffee +6 -0
  15. data/app/assets/javascripts/components/song_component/song_component.directive.coffee +6 -0
  16. data/app/assets/javascripts/components/songbook/songbook.controller.coffee +29 -0
  17. data/app/assets/javascripts/components/songs/song.controller.coffee +10 -0
  18. data/app/assets/javascripts/components/songs/song.directive.coffee +7 -0
  19. data/app/assets/javascripts/components/songs/song.service.coffee +98 -0
  20. data/app/assets/javascripts/components/songs/songs.controller.coffee +21 -0
  21. data/app/assets/javascripts/routes.coffee +11 -0
  22. data/app/assets/stylesheets/application.scss +34 -0
  23. data/app/fonts/DejaVuSans-Bold.ttf +0 -0
  24. data/app/fonts/DejaVuSans-BoldOblique.ttf +0 -0
  25. data/app/fonts/DejaVuSans-ExtraLight.ttf +0 -0
  26. data/app/fonts/DejaVuSans-Oblique.ttf +0 -0
  27. data/app/fonts/DejaVuSans.ttf +0 -0
  28. data/app/fonts/DejaVuSansCondensed-Bold.ttf +0 -0
  29. data/app/fonts/DejaVuSansCondensed-BoldOblique.ttf +0 -0
  30. data/app/fonts/DejaVuSansCondensed-Oblique.ttf +0 -0
  31. data/app/fonts/DejaVuSansCondensed.ttf +0 -0
  32. data/app/fonts/DejaVuSansMono-Bold.ttf +0 -0
  33. data/app/fonts/DejaVuSansMono-BoldOblique.ttf +0 -0
  34. data/app/fonts/DejaVuSansMono-Oblique.ttf +0 -0
  35. data/app/fonts/DejaVuSansMono.ttf +0 -0
  36. data/app/fonts/DejaVuSerif-Bold.ttf +0 -0
  37. data/app/fonts/DejaVuSerif-BoldItalic.ttf +0 -0
  38. data/app/fonts/DejaVuSerif-Italic.ttf +0 -0
  39. data/app/fonts/DejaVuSerif.ttf +0 -0
  40. data/app/fonts/DejaVuSerifCondensed-Bold.ttf +0 -0
  41. data/app/fonts/DejaVuSerifCondensed-BoldItalic.ttf +0 -0
  42. data/app/fonts/DejaVuSerifCondensed-Italic.ttf +0 -0
  43. data/app/fonts/DejaVuSerifCondensed.ttf +0 -0
  44. data/app/tex/songbook.tex +12 -0
  45. data/app/views/application.haml +54 -0
  46. data/app/views/layouts/application.haml +40 -0
  47. data/app/views/songbook.prawn +80 -0
  48. data/app/views/songs/component.haml +2 -0
  49. data/app/views/songs/index.jbuilder +5 -0
  50. data/app/views/songs/show.haml +23 -0
  51. data/app/views/songs/show.jbuilder +24 -0
  52. data/app/views/songs/song.haml +4 -0
  53. data/exe/sb_server +6 -0
  54. data/lib/prawn_ext/font/afm.rb +20 -0
  55. data/lib/songbooks.rb +32 -0
  56. data/lib/songbooks/CLI.rb +13 -0
  57. data/lib/songbooks/components/chord.rb +98 -0
  58. data/lib/songbooks/components/literal.rb +57 -0
  59. data/lib/songbooks/components/metadata.rb +69 -0
  60. data/lib/songbooks/components/section.rb +73 -0
  61. data/lib/songbooks/controllers/controller.rb +18 -0
  62. data/lib/songbooks/controllers/songs_controller.rb +30 -0
  63. data/lib/songbooks/folder.rb +32 -0
  64. data/lib/songbooks/server.rb +65 -0
  65. data/lib/songbooks/song.rb +53 -0
  66. data/lib/songbooks/version.rb +3 -0
  67. data/lib/songbooks/view_helper.rb +9 -0
  68. data/public/javascripts/angular-local-storage.js +449 -0
  69. data/public/javascripts/angular-route.js +991 -0
  70. data/public/javascripts/angular.js +28904 -0
  71. data/public/javascripts/application.js +8 -0
  72. data/public/javascripts/components/filters/convertWhitespace.filter.js +8 -0
  73. data/public/javascripts/components/song_component/chord.directive.js +13 -0
  74. data/public/javascripts/components/song_component/literal.directive.js +9 -0
  75. data/public/javascripts/components/song_component/song_component.directive.js +9 -0
  76. data/public/javascripts/components/songbook/songbook.controller.js +33 -0
  77. data/public/javascripts/components/songs/song.controller.js +12 -0
  78. data/public/javascripts/components/songs/song.directive.js +9 -0
  79. data/public/javascripts/components/songs/song.service.js +107 -0
  80. data/public/javascripts/components/songs/songs.controller.js +22 -0
  81. data/public/javascripts/jquery-2.1.4.min.js +4 -0
  82. data/public/javascripts/routes.js +16 -0
  83. data/public/stylesheets/application.css +24 -0
  84. data/public/stylesheets/bootstrap-theme.min.css +5 -0
  85. data/public/stylesheets/bootstrap.min.css +5 -0
  86. data/songbooks.gemspec +51 -0
  87. metadata +369 -0
@@ -0,0 +1,57 @@
1
+ require_relative 'chord'
2
+
3
+ module Songbooks
4
+ module Components
5
+ class Literal
6
+
7
+ #----------------------------------------------------------------
8
+ # Parsing
9
+ #----------------------------------------------------------------
10
+
11
+ #
12
+ # Tries to munch a literal from the given string.
13
+ # If the string starts with a valid literal, it is automatically
14
+ # removed from the string
15
+ #
16
+ # @return [Songbooks::Components::Literal, NilClass] a Literal containing the munched
17
+ # text or +nil+ if the string didn't start with a literal
18
+ #
19
+ def self.munch_literal(text)
20
+ result = ''
21
+
22
+ until text.length.zero?
23
+ # Munch leading whitespace
24
+ if text =~ /^(\s+)/
25
+ result << text.slice!(0...$1.length)
26
+ end
27
+
28
+ # If we reached a chord, we may not munch any further
29
+ break if Chord.starts_with_chord?(text)
30
+
31
+ # Munch non-chord, non whitespace characters
32
+ if text =~/^([^\s+])/
33
+ result << text.slice!(0...$1.length)
34
+ end
35
+ end
36
+
37
+ result == '' ? nil : Literal.new(result)
38
+ end
39
+
40
+ def initialize(string)
41
+ @string = string
42
+ end
43
+
44
+ def to_s
45
+ @string
46
+ end
47
+
48
+ def to_str
49
+ @string
50
+ end
51
+
52
+ def html_string
53
+ @string
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,69 @@
1
+ module Songbooks
2
+ module Components
3
+ class Metadata
4
+
5
+ #----------------------------------------------------------------
6
+ # CONSTANTS
7
+ #----------------------------------------------------------------
8
+
9
+ EMPTY_LINE_REGEXP = /\A\s*\r?\n/
10
+
11
+ REGEXPS = {
12
+ :title => /\A@title\s*\r?\n?([^\n\r]+)/,
13
+ :artist => /\A@artist\s*\r?\n?([^\n\r]+)/,
14
+ }
15
+
16
+ #----------------------------------------------------------------
17
+ # Parsing
18
+ #----------------------------------------------------------------
19
+
20
+ #
21
+ # Tries to parse metadata from the header of a given string.
22
+ # It automatically deletes empty / whitespace lines and the parsed
23
+ # content from the given string.
24
+ #
25
+ # Currently supported metadata/annotations
26
+ # @artist
27
+ # @title
28
+ #
29
+ # @param [String] text
30
+ # The string which possibly contains header data, usually a whole song file content
31
+ #
32
+ # @return [Songbooks::Components::Metadata]
33
+ # The parsed metadata with only the options set that could be parsed.
34
+ #
35
+ def self.munch_header(text)
36
+ found = true
37
+ options = {}
38
+
39
+ while found
40
+ found = false
41
+
42
+ if (match = text.match(EMPTY_LINE_REGEXP))
43
+ text.slice!(0...match[0].length)
44
+ found = true
45
+ else
46
+ REGEXPS.each do |name, regexp|
47
+ if (match = text.match(regexp))
48
+ options[name] = match[1]
49
+ text.slice!(0...match[0].length)
50
+ found = true
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ Metadata.new(options)
57
+ end
58
+
59
+ def initialize(options = {})
60
+ options.each do |k, v|
61
+ instance_variable_set("@#{k}", v)
62
+ end
63
+ end
64
+
65
+ attr_reader :artist, :title
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,73 @@
1
+ require 'songbooks/components/chord'
2
+ require 'songbooks/components/literal'
3
+
4
+ module Songbooks
5
+ module Components
6
+ class Section
7
+
8
+ #----------------------------------------------------------------
9
+ # CONSTANTS
10
+ #----------------------------------------------------------------
11
+
12
+ REGEXP = /^##\s*(.*)$/
13
+
14
+ #----------------------------------------------------------------
15
+ # Parsing
16
+ #----------------------------------------------------------------
17
+
18
+ #
19
+ # Tries to parse the given text line as a section name identifier
20
+ #
21
+ # @return [Hash, NilClass] the parsed information or +nil+ if the given row
22
+ # is not a valid section row
23
+ #
24
+ def self.parse_information(content)
25
+ if content =~ REGEXP
26
+ return {name: $1}
27
+ end
28
+ nil
29
+ end
30
+
31
+ def initialize(name, initial = false)
32
+ @name = name
33
+ @content_lines = []
34
+ @initial = initial
35
+ end
36
+
37
+ #
38
+ # Parses the given line (chords and literals) and adds it to this section
39
+ #
40
+ def parse_line!(_line)
41
+ line = _line.dup.rstrip
42
+ line_components = []
43
+
44
+ until line.length.zero?
45
+ component = Songbooks::Components::Chord.munch_chord(line)
46
+ component ||= Songbooks::Components::Literal.munch_literal(line)
47
+ line_components << component
48
+ end
49
+
50
+ add_line(line_components)
51
+ end
52
+
53
+ def add_line(line)
54
+ @content_lines << line
55
+ end
56
+
57
+ def name
58
+ @name == 'Initial' ? '' : @name
59
+ end
60
+
61
+ def lines
62
+ @content_lines
63
+ end
64
+
65
+ def transpose!(amount)
66
+ lines.flatten.each do |component|
67
+ next unless component.is_a?(Songbooks::Components::Chord)
68
+ component.transpose!(amount)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,18 @@
1
+ module Songbooks
2
+ module Controllers
3
+ class Controller
4
+
5
+ def template
6
+ @template.to_sym
7
+ end
8
+
9
+ def layout
10
+ (@layout || 'layouts/application').to_sym
11
+ end
12
+
13
+ def resulting_instance_variables
14
+ instance_variables - [:@layout, :@params, :@template]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ require 'songbooks/controllers/controller'
2
+
3
+ module Songbooks
4
+ module Controllers
5
+ class SongsController < Controller
6
+
7
+ def initialize(params = {}, session = {})
8
+ @params = params
9
+ @session = session
10
+ end
11
+
12
+ attr_reader :session, :params
13
+
14
+ def index
15
+ @songs = Songbooks.folder.songs
16
+ @template = 'songs/index'
17
+ end
18
+
19
+ def show
20
+ @song = Songbooks.folder.song(params['identifier'])
21
+ @template = 'songs/show'
22
+ end
23
+
24
+ def generate
25
+ @songs = @session['songbook'].map { |id| Songbooks.folder.song(id) }
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,32 @@
1
+ require 'songbooks/song'
2
+
3
+ module Songbooks
4
+ class Folder
5
+ def initialize(path)
6
+ @path = Pathname.new(path)
7
+ end
8
+
9
+ attr_reader :path
10
+
11
+ def song(identifier)
12
+ songs_by_identifier[identifier]
13
+ end
14
+
15
+ def songs
16
+ songs_by_identifier.values
17
+ end
18
+
19
+ private
20
+
21
+ def songs_by_identifier
22
+ @songs ||= chords_files.each_with_object({}) do |file_path, h|
23
+ song = Songbooks::Song.new(File.read(file_path))
24
+ h[song.identifier] = song
25
+ end
26
+ end
27
+
28
+ def chords_files
29
+ Dir[@path.join('**', '*.txt')]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,65 @@
1
+ require 'sinatra/base'
2
+ require 'songbooks/controllers/songs_controller'
3
+ require 'songbooks/view_helper'
4
+
5
+ module Songbooks
6
+ class Server < Sinatra::Base
7
+ include Sinatra::Prawn::Helpers
8
+
9
+ configure do
10
+ enable :sessions
11
+
12
+ set :views, Songbooks.root.join('app', 'views').to_s
13
+ set :public_folder, Songbooks.root.join('public').to_s
14
+
15
+ set :prawn, { :page_layout => :portrait, :page_size => 'A4' }
16
+ end
17
+
18
+ get '/' do
19
+ haml :application
20
+ end
21
+
22
+ get '/songs.json' do
23
+ controller = Songbooks::Controllers::SongsController.new(params)
24
+ controller.index
25
+
26
+ controller.resulting_instance_variables.each do |var_name|
27
+ instance_variable_set(var_name, controller.instance_variable_get(var_name))
28
+ end
29
+
30
+ jbuilder controller.template
31
+ end
32
+
33
+ get '/songs/:identifier.json' do
34
+ controller = Songbooks::Controllers::SongsController.new(params)
35
+ controller.show
36
+
37
+ controller.resulting_instance_variables.each do |var_name|
38
+ instance_variable_set(var_name, controller.instance_variable_get(var_name))
39
+ end
40
+
41
+ jbuilder controller.template
42
+ end
43
+
44
+ post '/songbook' do
45
+ @params = JSON.parse(request.body.read)
46
+ session[:songbook] = params['songs']
47
+ end
48
+
49
+ get '/songbook.pdf' do
50
+ controller = Songbooks::Controllers::SongsController.new(params, session)
51
+ controller.generate
52
+
53
+ controller.resulting_instance_variables.each do |var_name|
54
+ instance_variable_set(var_name, controller.instance_variable_get(var_name))
55
+ end
56
+
57
+ content_type 'application/pdf'
58
+ prawn :songbook
59
+ end
60
+
61
+ get '/static/*' do
62
+ haml params['splat'].first.to_sym
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,53 @@
1
+ require 'songbooks/components/section'
2
+ require 'songbooks/components/metadata'
3
+
4
+ module Songbooks
5
+ class Song
6
+ attr_reader :artist, :title, :identifier
7
+
8
+ def initialize(content)
9
+ parse_content(content)
10
+ end
11
+
12
+ def identifier
13
+ @identifier ||= [artist, title].join('-').gsub(/[^a-zA-Z\-_0-9]/, '_')
14
+ end
15
+
16
+ def sections
17
+ @sections ||= []
18
+ end
19
+
20
+ def longest_section_caption
21
+ sections.map(&:name).sort_by(&:length).last
22
+ end
23
+
24
+ private
25
+
26
+ def parse_content(content)
27
+ current_section = Songbooks::Components::Section.new('Initial', true)
28
+
29
+ meta = Songbooks::Components::Metadata.munch_header(content)
30
+ @artist, @title = meta.artist, meta.title
31
+
32
+ content.lines.each do |line|
33
+ if (information = Songbooks::Components::Section.parse_information(line))
34
+ self.sections << current_section
35
+ current_section = Songbooks::Components::Section.new(information[:name])
36
+ else
37
+ current_section.parse_line!(line)
38
+ end
39
+ end
40
+
41
+ self.sections << current_section
42
+ end
43
+
44
+ def transpose!(count)
45
+ return if count == 0
46
+ sections.each { |section| section.transpose!(count) }
47
+ end
48
+
49
+ def transpose(count)
50
+ dup.transpose!(count)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Songbooks
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,9 @@
1
+ module Songbooks
2
+ module ViewHelper
3
+ def self.javascripts
4
+ Dir[Songbooks.root.join('public', 'javascripts', 'components', '**', '*.js').to_s].map do |filename|
5
+ filename.sub(Songbooks.root.join('public', 'javascripts/').to_s, '')
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,449 @@
1
+ /**
2
+ * An Angular module that gives you access to the browsers local storage
3
+ * @version v0.2.3 - 2015-10-11
4
+ * @link https://github.com/grevory/angular-local-storage
5
+ * @author grevory <greg@gregpike.ca>
6
+ * @license MIT License, http://www.opensource.org/licenses/MIT
7
+ */
8
+ (function ( window, angular, undefined ) {
9
+ /*jshint globalstrict:true*/
10
+ 'use strict';
11
+
12
+ var isDefined = angular.isDefined,
13
+ isUndefined = angular.isUndefined,
14
+ isNumber = angular.isNumber,
15
+ isObject = angular.isObject,
16
+ isArray = angular.isArray,
17
+ extend = angular.extend,
18
+ toJson = angular.toJson;
19
+ var angularLocalStorage = angular.module('LocalStorageModule', []);
20
+
21
+ angularLocalStorage.provider('localStorageService', function() {
22
+
23
+ // You should set a prefix to avoid overwriting any local storage variables from the rest of your app
24
+ // e.g. localStorageServiceProvider.setPrefix('yourAppName');
25
+ // With provider you can use config as this:
26
+ // myApp.config(function (localStorageServiceProvider) {
27
+ // localStorageServiceProvider.prefix = 'yourAppName';
28
+ // });
29
+ this.prefix = 'ls';
30
+
31
+ // You could change web storage type localstorage or sessionStorage
32
+ this.storageType = 'localStorage';
33
+
34
+ // Cookie options (usually in case of fallback)
35
+ // expiry = Number of days before cookies expire // 0 = Does not expire
36
+ // path = The web path the cookie represents
37
+ this.cookie = {
38
+ expiry: 30,
39
+ path: '/'
40
+ };
41
+
42
+ // Send signals for each of the following actions?
43
+ this.notify = {
44
+ setItem: true,
45
+ removeItem: false
46
+ };
47
+
48
+ // Setter for the prefix
49
+ this.setPrefix = function(prefix) {
50
+ this.prefix = prefix;
51
+ return this;
52
+ };
53
+
54
+ // Setter for the storageType
55
+ this.setStorageType = function(storageType) {
56
+ this.storageType = storageType;
57
+ return this;
58
+ };
59
+
60
+ // Setter for cookie config
61
+ this.setStorageCookie = function(exp, path) {
62
+ this.cookie.expiry = exp;
63
+ this.cookie.path = path;
64
+ return this;
65
+ };
66
+
67
+ // Setter for cookie domain
68
+ this.setStorageCookieDomain = function(domain) {
69
+ this.cookie.domain = domain;
70
+ return this;
71
+ };
72
+
73
+ // Setter for notification config
74
+ // itemSet & itemRemove should be booleans
75
+ this.setNotify = function(itemSet, itemRemove) {
76
+ this.notify = {
77
+ setItem: itemSet,
78
+ removeItem: itemRemove
79
+ };
80
+ return this;
81
+ };
82
+
83
+ this.$get = ['$rootScope', '$window', '$document', '$parse', function($rootScope, $window, $document, $parse) {
84
+ var self = this;
85
+ var prefix = self.prefix;
86
+ var cookie = self.cookie;
87
+ var notify = self.notify;
88
+ var storageType = self.storageType;
89
+ var webStorage;
90
+
91
+ // When Angular's $document is not available
92
+ if (!$document) {
93
+ $document = document;
94
+ } else if ($document[0]) {
95
+ $document = $document[0];
96
+ }
97
+
98
+ // If there is a prefix set in the config lets use that with an appended period for readability
99
+ if (prefix.substr(-1) !== '.') {
100
+ prefix = !!prefix ? prefix + '.' : '';
101
+ }
102
+ var deriveQualifiedKey = function(key) {
103
+ return prefix + key;
104
+ };
105
+ // Checks the browser to see if local storage is supported
106
+ var browserSupportsLocalStorage = (function () {
107
+ try {
108
+ var supported = (storageType in $window && $window[storageType] !== null);
109
+
110
+ // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
111
+ // is available, but trying to call .setItem throws an exception.
112
+ //
113
+ // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage
114
+ // that exceeded the quota."
115
+ var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7));
116
+ if (supported) {
117
+ webStorage = $window[storageType];
118
+ webStorage.setItem(key, '');
119
+ webStorage.removeItem(key);
120
+ }
121
+
122
+ return supported;
123
+ } catch (e) {
124
+ storageType = 'cookie';
125
+ $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
126
+ return false;
127
+ }
128
+ }());
129
+
130
+ // Directly adds a value to local storage
131
+ // If local storage is not available in the browser use cookies
132
+ // Example use: localStorageService.add('library','angular');
133
+ var addToLocalStorage = function (key, value) {
134
+ // Let's convert undefined values to null to get the value consistent
135
+ if (isUndefined(value)) {
136
+ value = null;
137
+ } else {
138
+ value = toJson(value);
139
+ }
140
+
141
+ // If this browser does not support local storage use cookies
142
+ if (!browserSupportsLocalStorage || self.storageType === 'cookie') {
143
+ if (!browserSupportsLocalStorage) {
144
+ $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
145
+ }
146
+
147
+ if (notify.setItem) {
148
+ $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'});
149
+ }
150
+ return addToCookies(key, value);
151
+ }
152
+
153
+ try {
154
+ if (webStorage) {
155
+ webStorage.setItem(deriveQualifiedKey(key), value);
156
+ }
157
+ if (notify.setItem) {
158
+ $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType});
159
+ }
160
+ } catch (e) {
161
+ $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
162
+ return addToCookies(key, value);
163
+ }
164
+ return true;
165
+ };
166
+
167
+ // Directly get a value from local storage
168
+ // Example use: localStorageService.get('library'); // returns 'angular'
169
+ var getFromLocalStorage = function (key) {
170
+
171
+ if (!browserSupportsLocalStorage || self.storageType === 'cookie') {
172
+ if (!browserSupportsLocalStorage) {
173
+ $rootScope.$broadcast('LocalStorageModule.notification.warning','LOCAL_STORAGE_NOT_SUPPORTED');
174
+ }
175
+
176
+ return getFromCookies(key);
177
+ }
178
+
179
+ var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null;
180
+ // angular.toJson will convert null to 'null', so a proper conversion is needed
181
+ // FIXME not a perfect solution, since a valid 'null' string can't be stored
182
+ if (!item || item === 'null') {
183
+ return null;
184
+ }
185
+
186
+ try {
187
+ return JSON.parse(item);
188
+ } catch (e) {
189
+ return item;
190
+ }
191
+ };
192
+
193
+ // Remove an item from local storage
194
+ // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular'
195
+ var removeFromLocalStorage = function () {
196
+ var i, key;
197
+ for (i=0; i<arguments.length; i++) {
198
+ key = arguments[i];
199
+ if (!browserSupportsLocalStorage || self.storageType === 'cookie') {
200
+ if (!browserSupportsLocalStorage) {
201
+ $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
202
+ }
203
+
204
+ if (notify.removeItem) {
205
+ $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'});
206
+ }
207
+ removeFromCookies(key);
208
+ }
209
+ else {
210
+ try {
211
+ webStorage.removeItem(deriveQualifiedKey(key));
212
+ if (notify.removeItem) {
213
+ $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {
214
+ key: key,
215
+ storageType: self.storageType
216
+ });
217
+ }
218
+ } catch (e) {
219
+ $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
220
+ removeFromCookies(key);
221
+ }
222
+ }
223
+ }
224
+ };
225
+
226
+ // Return array of keys for local storage
227
+ // Example use: var keys = localStorageService.keys()
228
+ var getKeysForLocalStorage = function () {
229
+
230
+ if (!browserSupportsLocalStorage) {
231
+ $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
232
+ return false;
233
+ }
234
+
235
+ var prefixLength = prefix.length;
236
+ var keys = [];
237
+ for (var key in webStorage) {
238
+ // Only return keys that are for this app
239
+ if (key.substr(0,prefixLength) === prefix) {
240
+ try {
241
+ keys.push(key.substr(prefixLength));
242
+ } catch (e) {
243
+ $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description);
244
+ return [];
245
+ }
246
+ }
247
+ }
248
+ return keys;
249
+ };
250
+
251
+ // Remove all data for this app from local storage
252
+ // Also optionally takes a regular expression string and removes the matching key-value pairs
253
+ // Example use: localStorageService.clearAll();
254
+ // Should be used mostly for development purposes
255
+ var clearAllFromLocalStorage = function (regularExpression) {
256
+
257
+ // Setting both regular expressions independently
258
+ // Empty strings result in catchall RegExp
259
+ var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp();
260
+ var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp();
261
+
262
+ if (!browserSupportsLocalStorage || self.storageType === 'cookie') {
263
+ if (!browserSupportsLocalStorage) {
264
+ $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
265
+ }
266
+ return clearAllFromCookies();
267
+ }
268
+
269
+ var prefixLength = prefix.length;
270
+
271
+ for (var key in webStorage) {
272
+ // Only remove items that are for this app and match the regular expression
273
+ if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) {
274
+ try {
275
+ removeFromLocalStorage(key.substr(prefixLength));
276
+ } catch (e) {
277
+ $rootScope.$broadcast('LocalStorageModule.notification.error',e.message);
278
+ return clearAllFromCookies();
279
+ }
280
+ }
281
+ }
282
+ return true;
283
+ };
284
+
285
+ // Checks the browser to see if cookies are supported
286
+ var browserSupportsCookies = (function() {
287
+ try {
288
+ return $window.navigator.cookieEnabled ||
289
+ ("cookie" in $document && ($document.cookie.length > 0 ||
290
+ ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1));
291
+ } catch (e) {
292
+ $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
293
+ return false;
294
+ }
295
+ }());
296
+
297
+ // Directly adds a value to cookies
298
+ // Typically used as a fallback is local storage is not available in the browser
299
+ // Example use: localStorageService.cookie.add('library','angular');
300
+ var addToCookies = function (key, value, daysToExpiry) {
301
+
302
+ if (isUndefined(value)) {
303
+ return false;
304
+ } else if(isArray(value) || isObject(value)) {
305
+ value = toJson(value);
306
+ }
307
+
308
+ if (!browserSupportsCookies) {
309
+ $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
310
+ return false;
311
+ }
312
+
313
+ try {
314
+ var expiry = '',
315
+ expiryDate = new Date(),
316
+ cookieDomain = '';
317
+
318
+ if (value === null) {
319
+ // Mark that the cookie has expired one day ago
320
+ expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000));
321
+ expiry = "; expires=" + expiryDate.toGMTString();
322
+ value = '';
323
+ } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) {
324
+ expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000));
325
+ expiry = "; expires=" + expiryDate.toGMTString();
326
+ } else if (cookie.expiry !== 0) {
327
+ expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000));
328
+ expiry = "; expires=" + expiryDate.toGMTString();
329
+ }
330
+ if (!!key) {
331
+ var cookiePath = "; path=" + cookie.path;
332
+ if(cookie.domain){
333
+ cookieDomain = "; domain=" + cookie.domain;
334
+ }
335
+ $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain;
336
+ }
337
+ } catch (e) {
338
+ $rootScope.$broadcast('LocalStorageModule.notification.error',e.message);
339
+ return false;
340
+ }
341
+ return true;
342
+ };
343
+
344
+ // Directly get a value from a cookie
345
+ // Example use: localStorageService.cookie.get('library'); // returns 'angular'
346
+ var getFromCookies = function (key) {
347
+ if (!browserSupportsCookies) {
348
+ $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
349
+ return false;
350
+ }
351
+
352
+ var cookies = $document.cookie && $document.cookie.split(';') || [];
353
+ for(var i=0; i < cookies.length; i++) {
354
+ var thisCookie = cookies[i];
355
+ while (thisCookie.charAt(0) === ' ') {
356
+ thisCookie = thisCookie.substring(1,thisCookie.length);
357
+ }
358
+ if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) {
359
+ var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length));
360
+ try {
361
+ return JSON.parse(storedValues);
362
+ } catch(e) {
363
+ return storedValues;
364
+ }
365
+ }
366
+ }
367
+ return null;
368
+ };
369
+
370
+ var removeFromCookies = function (key) {
371
+ addToCookies(key,null);
372
+ };
373
+
374
+ var clearAllFromCookies = function () {
375
+ var thisCookie = null, thisKey = null;
376
+ var prefixLength = prefix.length;
377
+ var cookies = $document.cookie.split(';');
378
+ for(var i = 0; i < cookies.length; i++) {
379
+ thisCookie = cookies[i];
380
+
381
+ while (thisCookie.charAt(0) === ' ') {
382
+ thisCookie = thisCookie.substring(1, thisCookie.length);
383
+ }
384
+
385
+ var key = thisCookie.substring(prefixLength, thisCookie.indexOf('='));
386
+ removeFromCookies(key);
387
+ }
388
+ };
389
+
390
+ var getStorageType = function() {
391
+ return storageType;
392
+ };
393
+
394
+ // Add a listener on scope variable to save its changes to local storage
395
+ // Return a function which when called cancels binding
396
+ var bindToScope = function(scope, key, def, lsKey) {
397
+ lsKey = lsKey || key;
398
+ var value = getFromLocalStorage(lsKey);
399
+
400
+ if (value === null && isDefined(def)) {
401
+ value = def;
402
+ } else if (isObject(value) && isObject(def)) {
403
+ value = extend(def, value);
404
+ }
405
+
406
+ $parse(key).assign(scope, value);
407
+
408
+ return scope.$watch(key, function(newVal) {
409
+ addToLocalStorage(lsKey, newVal);
410
+ }, isObject(scope[key]));
411
+ };
412
+
413
+ // Return localStorageService.length
414
+ // ignore keys that not owned
415
+ var lengthOfLocalStorage = function() {
416
+ var count = 0;
417
+ var storage = $window[storageType];
418
+ for(var i = 0; i < storage.length; i++) {
419
+ if(storage.key(i).indexOf(prefix) === 0 ) {
420
+ count++;
421
+ }
422
+ }
423
+ return count;
424
+ };
425
+
426
+ return {
427
+ isSupported: browserSupportsLocalStorage,
428
+ getStorageType: getStorageType,
429
+ set: addToLocalStorage,
430
+ add: addToLocalStorage, //DEPRECATED
431
+ get: getFromLocalStorage,
432
+ keys: getKeysForLocalStorage,
433
+ remove: removeFromLocalStorage,
434
+ clearAll: clearAllFromLocalStorage,
435
+ bind: bindToScope,
436
+ deriveKey: deriveQualifiedKey,
437
+ length: lengthOfLocalStorage,
438
+ cookie: {
439
+ isSupported: browserSupportsCookies,
440
+ set: addToCookies,
441
+ add: addToCookies, //DEPRECATED
442
+ get: getFromCookies,
443
+ remove: removeFromCookies,
444
+ clearAll: clearAllFromCookies
445
+ }
446
+ };
447
+ }];
448
+ });
449
+ })( window, window.angular );