billy_cms 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +4 -0
  4. data/CODE_OF_CONDUCT.md +49 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +41 -0
  8. data/Rakefile +10 -0
  9. data/app/assets/images/billy_cms/32px.png +0 -0
  10. data/app/assets/images/billy_cms/40px.png +0 -0
  11. data/app/assets/images/billy_cms/fileicon.png +0 -0
  12. data/app/assets/images/billy_cms/throbber.gif +0 -0
  13. data/app/assets/javascripts/billy_cms/backend/app.js.erb +217 -0
  14. data/app/assets/javascripts/billy_cms/backend/attributes.js +33 -0
  15. data/app/assets/javascripts/billy_cms/backend/billy_object.js +41 -0
  16. data/app/assets/javascripts/billy_cms/backend/directives/ng-jstree.js +200 -0
  17. data/app/assets/javascripts/billy_cms/backend/jstree.js +7781 -0
  18. data/app/assets/javascripts/billy_cms/backend/ngfileupload.js +2506 -0
  19. data/app/assets/javascripts/billy_cms/backend/page_type.js +11 -0
  20. data/app/assets/javascripts/billy_cms/backend/ui-router.js +4370 -0
  21. data/app/assets/javascripts/billy_cms/backend.js +15 -0
  22. data/app/assets/javascripts/billy_cms/frontend/api_connector.js +5 -0
  23. data/app/assets/javascripts/billy_cms/frontend/editing.es6 +120 -0
  24. data/app/assets/javascripts/billy_cms/frontend/google_maps.js +13 -0
  25. data/app/assets/javascripts/billy_cms/frontend/navbar.js +0 -0
  26. data/app/assets/javascripts/billy_cms/frontend/slick.es6 +8 -0
  27. data/app/assets/javascripts/billy_cms.js +16 -0
  28. data/app/assets/stylesheets/billy_cms/backend/index.scss +17 -0
  29. data/app/assets/stylesheets/billy_cms/editmode.scss +78 -0
  30. data/app/assets/stylesheets/billy_cms/index.scss +5 -0
  31. data/app/assets/stylesheets/billy_cms/jstree.scss +1061 -0
  32. data/app/assets/stylesheets/billy_cms.css +15 -0
  33. data/app/controllers/billy_cms/api/api_controller.rb +25 -0
  34. data/app/controllers/billy_cms/api/attributes_controller.rb +65 -0
  35. data/app/controllers/billy_cms/api/page_types_controller.rb +13 -0
  36. data/app/controllers/billy_cms/api/pages_controller.rb +63 -0
  37. data/app/controllers/billy_cms/backend_controller.rb +10 -0
  38. data/app/controllers/billy_cms/base_controller.rb +23 -0
  39. data/app/helpers/billy_cms/cms_path_helper.rb +8 -0
  40. data/app/helpers/billy_cms/content_helper.rb +17 -0
  41. data/app/helpers/billy_cms/permission_helper.rb +24 -0
  42. data/app/models/billy_cms/additional_attribute.rb +6 -0
  43. data/app/models/billy_cms/additional_attributes_page_types.rb +6 -0
  44. data/app/models/billy_cms/page.rb +61 -0
  45. data/app/models/billy_cms/page_type.rb +7 -0
  46. data/app/models/billy_router.rb +20 -0
  47. data/app/serializers/billy_cms/additional_attribute_serializer.rb +6 -0
  48. data/app/serializers/billy_cms/page_serializer.rb +15 -0
  49. data/app/serializers/billy_cms/page_type_serializer.rb +5 -0
  50. data/app/views/billy_cms/_edit_page_button.html.erb +1 -0
  51. data/app/views/billy_cms/_page_settings_form.html.erb +25 -0
  52. data/app/views/billy_cms/_page_settings_modal.html.erb +19 -0
  53. data/app/views/billy_cms/_settings_page_button.html.erb +4 -0
  54. data/app/views/billy_cms/backend/_attributes.html.erb +12 -0
  55. data/app/views/billy_cms/backend/_header.html.erb +84 -0
  56. data/app/views/billy_cms/backend/_tree.html.erb +58 -0
  57. data/app/views/billy_cms/backend/index.html.erb +19 -0
  58. data/billy_cms.gemspec +32 -0
  59. data/bin/console +14 -0
  60. data/bin/setup +8 -0
  61. data/lib/billy_cms/version.rb +3 -0
  62. data/lib/billy_cms.rb +5 -0
  63. metadata +147 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 74961fb2e827597e5224a2704cf41904160df03c
4
+ data.tar.gz: 42f4570be2153d4bb0ff8a7d94d7e35ae56860f7
5
+ SHA512:
6
+ metadata.gz: 2aa183c87bb15c0af81d2ef713e5f64ca66287744cb5231f19dfbf9ef040d181023a3e4d5bf9c8e32bf8a135283b1ced02f860d6f97db2c641d1f351b117094c
7
+ data.tar.gz: a047dab8550cd4e60b10b8aa3fedbefb84ab6b7ea8c77d09410d1fb8453a51bb997d649751df0df70256d6175b3ec694147da8b4831e6e7d58e6666e18b9cf74
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at arinaldoni@me.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in billy_cms.gemspec
4
+ gemspec
5
+
6
+ gem 'jquery-rails'
7
+ gem 'friendly_id', '~> 5.1.0'
8
+ gem 'active_model_serializers', '~> 0.9.3'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Alexis Rinaldoni
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # BillyCms
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/billy_cms`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'billy_cms'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install billy_cms
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/billy_cms. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,217 @@
1
+ var app = angular.module('BillyCmsBackend', [
2
+ 'ngJsTree',
3
+ 'ui.tinymce',
4
+ 'ngMaterial',
5
+ 'ngFileUpload',
6
+ 'ui.router'
7
+ ]);
8
+
9
+ app.run(function() {
10
+
11
+ });
12
+
13
+ app.config(function($stateProvider, $urlRouterProvider) {
14
+ $urlRouterProvider.otherwise('/tree');
15
+
16
+ $stateProvider
17
+ .state('tree', {
18
+ url: '/tree',
19
+ templateUrl: 'tree/tree.html'
20
+ })
21
+ .state('page_types', {
22
+ url: '/system/types',
23
+ templateUrl: 'page_types/page_types.html'
24
+ })
25
+ .state('additional_attributes', {
26
+ url: '/system/attributes',
27
+ templateUrl: 'attributes/attributes.html',
28
+ controller: 'AttributesCtrl as attrs'
29
+ })
30
+ });
31
+
32
+ $(function() {
33
+ $.jstree.defaults.types['Binary'] = { icon: 'binary-type' };
34
+ });
35
+
36
+ app.controller('ApplicationCtrl', ['$scope', '$http', 'BillyObject', 'PageType', function($scope, $http, BillyObject, PageType) {
37
+ var ctrl = this;
38
+ this.rawPagesData = [];
39
+ this.treeConfig = {
40
+ core : {
41
+ multiple : false,
42
+ animation: true,
43
+ error : function(error) {
44
+ console.error('treeCtrl: error from js tree - ' + angular.toJson(error));
45
+ },
46
+ check_callback : true,
47
+ worker : true
48
+ },
49
+ plugins: ['sort', 'state', 'types'],
50
+ sort: function(node1, node2) {
51
+ var sortkey1 = ctrl.findPageById(node1).order_key || 0;
52
+ var sortkey2 = ctrl.findPageById(node2).order_key || 0;
53
+ return (sortkey2 < sortkey1) ? 1 : -1;
54
+ },
55
+ version : 1
56
+ };
57
+ $http.get('/billy_cms/api/page_types').then(function(result) {
58
+ ctrl.allPageTypes = result.data.page_types.map(function(pageTypeData) {
59
+ return new PageType(pageTypeData);
60
+ });
61
+ });
62
+ $http.get('/billy_cms/api/pages').then(function(result) {
63
+ ctrl.rawPagesData = result.data.pages.map(function(page) { return new BillyObject(page); });
64
+ ctrl.recreateTreeFromRawData();
65
+ });
66
+ this.tree = [];
67
+
68
+ this.recreateTreeFromRawData = function() {
69
+ ctrl.tree = ctrl.rawPagesData.map(function(page) { return page.jsTreeRepresentation(); });
70
+ ctrl.tree.push({
71
+ id: 'root',
72
+ parent: '#',
73
+ text: 'Rechtsanwaltskanzlei Arbeitsrecht Sabine Geilen'
74
+ });
75
+ ctrl.treeConfig.version++;
76
+ };
77
+
78
+ this.onTreeReady = function() {
79
+ $(this).on('changed.jstree', function(node, action) {
80
+ var jstree = $(this).jstree();
81
+ if (action.action !== 'select_node') {
82
+ return;
83
+ }
84
+ ctrl.selectedNode = ctrl.findPageById(jstree.get_selected()[0]);
85
+ $scope.$apply();
86
+ });
87
+ };
88
+
89
+ this.findPageById = function(id) {
90
+ return this.rawPagesData.filter(function(page) {
91
+ return id.toString() === page.id.toString();
92
+ })[0];
93
+ };
94
+
95
+ this.tinymceOptions = {
96
+ content_css: '<%= stylesheet_path 'application' %>',
97
+ plugins: 'advlist autolink anchor fullscreen code',
98
+ toolbar: 'undo redo bold italic underline fullscreen code',
99
+ menubar: '',
100
+ height: 350
101
+ };
102
+
103
+ this.saveNode = function(page) {
104
+ $http({
105
+ method: 'PATCH',
106
+ url: '/billy_cms/api/pages/' + page.id,
107
+ data: {
108
+ page: page
109
+ }
110
+ }).then(
111
+ function(response) {
112
+ if (response.status === 200) {
113
+ console.log('page saved');
114
+ console.log(response.data.page);
115
+ ctrl.recreateTreeFromRawData();
116
+ }
117
+ },
118
+ function(err) {
119
+ alert('Ein Fehler ist beim Speichern aufgetreten.');
120
+ console.error(err);
121
+ }
122
+ );
123
+ };
124
+
125
+ this.createNewPageForNode = function(node) {
126
+ var title = prompt('Titel der neuen Seite');
127
+ if (!title) {
128
+ return;
129
+ }
130
+ var pageData = {
131
+ title: title,
132
+ parent_page_id: node && node.id
133
+ };
134
+ $http.post('/billy_cms/api/pages', {
135
+ page: pageData
136
+ }).then(function(response) {
137
+ if (response.status === 200) {
138
+ ctrl.rawPagesData.push(new BillyObject(response.data.page));
139
+ ctrl.recreateTreeFromRawData();
140
+ }
141
+ }, function(response) {
142
+ alert('Fehler beim Erstellen der Seite: ', response.statusText);
143
+ });
144
+ };
145
+
146
+ this.createNewFileForNode = function(node) {
147
+ var title = prompt('Titel der Datei');
148
+ if (!title) {
149
+ return;
150
+ }
151
+ var pageData = {
152
+ title: title,
153
+ parent_page_id: node && node.id,
154
+ page_type: 'Binary'
155
+ };
156
+ $http.post('/billy_cms/api/pages', {
157
+ page: pageData
158
+ }).then(function(response) {
159
+ if (response.status === 200) {
160
+ ctrl.rawPagesData.push(new BillyObject(response.data.page));
161
+ ctrl.recreateTreeFromRawData();
162
+ }
163
+ }, function(response) {
164
+ alert('Fehler beim Erstellen der Datei: ', response.statusText);
165
+ });
166
+ };
167
+
168
+ this.deleteNode = function(node) {
169
+ if (!node || !node.id) {
170
+ alert('Keine gültige Seite ausgewählt!');
171
+ return;
172
+ }
173
+ var message = 'Die Seite "' + node.title + '" wirklich löschen? Das ist unwiederruflich!';
174
+ if (window.confirm(message)) {
175
+ $http.delete('/billy_cms/api/pages/' + node.id).then(function(response) {
176
+ if (response.status === 200) {
177
+ var page = ctrl.findPageById(node.id);
178
+ if (!page) {
179
+ return;
180
+ }
181
+ var pageIndex = ctrl.rawPagesData.indexOf(page);
182
+ if (pageIndex > -1) {
183
+ ctrl.rawPagesData.splice(pageIndex, 1);
184
+ ctrl.recreateTreeFromRawData();
185
+ }
186
+ }
187
+ }, function(response) {
188
+ alert('Fehler beim Löschen der Datei: ', response.status);
189
+ });
190
+ }
191
+ };
192
+
193
+ this.setBinaryContent = function($file, node) {
194
+ if (!$file) {
195
+ node.content = '';
196
+ return;
197
+ }
198
+ var reader = new FileReader();
199
+
200
+ reader.onloadend = function() {
201
+ var result = reader.result;
202
+ node.content = result;
203
+ $scope.$apply();
204
+ }
205
+ reader.readAsDataURL($file);
206
+ };
207
+
208
+ this.openInPreview = function(node) {
209
+ window.open(node.cms_path || ('/' + node.id));
210
+ };
211
+ }]);
212
+
213
+ app.directive('billyHeaderToolbar', function() {
214
+ return {
215
+ templateUrl: 'header/header.html'
216
+ };
217
+ });
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ angular.module('BillyCmsBackend').controller('AttributesCtrl', function($http, Attribute) {
4
+ var vm = this;
5
+
6
+ vm.possibleAttributes = [];
7
+ vm.loadAttributes = function() {
8
+ $http.get('/billy_cms/api/attributes')
9
+ .then(function(response) {
10
+ var data = response.data;
11
+ console.log(data);
12
+ vm.possibleAttributes = data.attributes.map(function(attr) {
13
+ return new Attribute(attr);
14
+ })
15
+ }, function(err) {
16
+ alert('Fehler: ', err);
17
+ });
18
+ };
19
+ vm.loadAttributes();
20
+
21
+ return vm;
22
+ });
23
+
24
+ angular.module('BillyCmsBackend').factory('Attribute', function() {
25
+ var Attribute = function(options) {
26
+ this.title = options.title || '';
27
+ this.name = options.name || null;
28
+ this.id = options.id || null;
29
+ this.attribute_type = options.attribute_type || 'string';
30
+ };
31
+
32
+ return Attribute;
33
+ });
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ angular.module('BillyCmsBackend').factory('BillyObject', function() {
4
+ var BillyObject = function(values) {
5
+ for (var key in values) {
6
+ this[key] = values[key];
7
+ }
8
+ };
9
+
10
+ BillyObject.prototype.jsTreeRepresentation = function() {
11
+ return {
12
+ id: this.id,
13
+ parent: this.parent_page_id || 'root',
14
+ text: this.title,
15
+ type: !this.page_type || this.page_type === 'Standard' ? 'default' : this.page_type
16
+ };
17
+ };
18
+
19
+ BillyObject.prototype.getDataType = function() {
20
+ if (this.__mime) {
21
+ return this.__mime;
22
+ }
23
+ if (this.page_type === 'Binary') {
24
+ if (this.content) {
25
+ var splitted = this.content.split(';base64,');
26
+ if (splitted.length > 1) {
27
+ this.__mime = splitted[0].replace('data:', '');
28
+ return this.__mime;
29
+ }
30
+ }
31
+ }
32
+ return 'text/html';
33
+ };
34
+
35
+ BillyObject.prototype.isDataType = function(type) {
36
+ var mime = this.getDataType();
37
+ return new RegExp(type + '/\w*').test(mime);
38
+ };
39
+
40
+ return BillyObject
41
+ });
@@ -0,0 +1,200 @@
1
+ (function (angular) {
2
+ 'use strict';
3
+
4
+ //// JavaScript Code ////
5
+ function jsTreeCtrl() {
6
+ /*jshint validthis:true */
7
+ var nodeSerialId = 1;
8
+
9
+ this.nodesFingerprint = function (e) {
10
+ if (!e.__uiNodeId) {
11
+ e.__uiNodeId = nodeSerialId++;
12
+ }
13
+ return '' + e.__uiNodeId + (e.id || '') + (e.text || '') + (e.type || '');
14
+ };
15
+
16
+ this.changeWatcher = function (arraySource, tokenFn) {
17
+ var self;
18
+ var getTokens = function () {
19
+ var result = [], token, el;
20
+ if (arraySource) {
21
+ var array = angular.isFunction(arraySource) ? arraySource() : arraySource;
22
+ for (var i = 0, n = array.length; i < n; i++) {
23
+ el = array[i];
24
+ token = tokenFn(el);
25
+ map[token] = el;
26
+ result.push(token);
27
+ }
28
+ }
29
+ return result;
30
+ };
31
+ // returns elements in that are in a but not in b
32
+ // subtractAsSets([4, 5, 6], [4, 5, 7]) => [6]
33
+ var subtractAsSets = function (a, b) {
34
+ var result = [], inB = {}, i, n;
35
+ for (i = 0, n = b.length; i < n; i++) {
36
+ inB[b[i]] = true;
37
+ }
38
+ for (i = 0, n = a.length; i < n; i++) {
39
+ if (!inB[a[i]]) {
40
+ result.push(a[i]);
41
+ }
42
+ }
43
+ return result;
44
+ };
45
+
46
+ // Map objects to tokens and vice-versa
47
+ var map = {};
48
+
49
+ var applyChanges = function (newTokens, oldTokens) {
50
+ var i, n, el, token;
51
+ var replacedTokens = {};
52
+ var removedTokens = subtractAsSets(oldTokens, newTokens);
53
+ for (i = 0, n = removedTokens.length; i < n; i++) {
54
+ var removedToken = removedTokens[i];
55
+ el = map[removedToken];
56
+ delete map[removedToken];
57
+ var newToken = tokenFn(el);
58
+ // if the element wasn't removed but simply got a new token, its old token will be different from the current one
59
+ if (newToken === removedToken) {
60
+ self.onRemoved(el);
61
+ } else {
62
+ replacedTokens[newToken] = removedToken;
63
+ self.onChanged(el);
64
+ }
65
+ }
66
+
67
+ var addedTokens = subtractAsSets(newTokens, oldTokens);
68
+ for (i = 0, n = addedTokens.length; i < n; i++) {
69
+ token = addedTokens[i];
70
+ el = map[token];
71
+ if (!replacedTokens[token]) {
72
+ self.onAdded(el);
73
+ }
74
+ }
75
+
76
+ };
77
+ self = {
78
+ subscribe: function (scope, onChanged) {
79
+ scope.$watch(getTokens, function (newTokens, oldTokens) {
80
+ if (!onChanged || onChanged(newTokens, oldTokens) !== false) {
81
+ applyChanges(newTokens, oldTokens);
82
+ }
83
+ }, true);
84
+ },
85
+ onAdded: angular.noop,
86
+ onChanged: angular.noop,
87
+ onRemoved: angular.noop
88
+ };
89
+ return self;
90
+ };
91
+ }
92
+
93
+ function jsTreeDirective() {
94
+ return {
95
+ restrict: 'A',
96
+ scope: {
97
+ treeData: '=ngModel',
98
+ shouldApply : '&'
99
+ },
100
+ controller: 'jsTreeCtrl',
101
+ link: function link(scope, elm, attrs, controller) {
102
+
103
+ var config = null,
104
+ nodesWatcher = controller.changeWatcher(scope.treeData, controller.nodesFingerprint);
105
+
106
+ var blocked = false;
107
+
108
+ function manageEvents(s, e, a) {
109
+ if (a.treeEvents) {
110
+ var evMap = a.treeEvents.split(';');
111
+ for (var i = 0; i < evMap.length; i++) {
112
+ if (evMap[i].length > 0) {
113
+ var name = evMap[i].split(':')[0];
114
+ var evt = name + '.jstree',
115
+ cb = evMap[i].split(':')[1];
116
+ s.tree.on(evt, s.$parent.$eval(cb));
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ function getOptions() {
123
+ var jsTreeSettings = attrs.jsTree ? scope.$parent.$eval(attrs.jsTree) : {};
124
+ config = {};
125
+ angular.copy(jsTreeSettings, config);
126
+ var result = JSON.stringify(config);
127
+ if (config.core) {
128
+ config.core.data = scope.treeData;
129
+ }
130
+ else {
131
+ config.core = { data: scope.treeData };
132
+ }
133
+ return result;
134
+ }
135
+
136
+ scope.destroy = function () {
137
+ if (attrs.tree) {
138
+ if (attrs.tree.indexOf('.') !== -1) {
139
+ var split = attrs.tree.split('.');
140
+ scope.tree = scope.$parent[split[0]][split[1]] = elm;
141
+ }
142
+ else {
143
+ scope.tree = scope.$parent[attrs.tree] = elm;
144
+ }
145
+
146
+ } else {
147
+ scope.tree = elm;
148
+ }
149
+ scope.tree.jstree('destroy');
150
+ };
151
+
152
+ scope.init = function () {
153
+ scope.tree.jstree(config);
154
+ manageEvents(scope, elm, attrs);
155
+ };
156
+
157
+ nodesWatcher.onChanged = function (node) {
158
+ if (angular.isDefined(scope.tree.jstree(true).set_type)) {
159
+ scope.tree.jstree(true).set_type(node.id, node.type);
160
+ }
161
+ scope.tree.jstree(true).rename_node(node.id, node.text);
162
+ };
163
+
164
+ nodesWatcher.onAdded = function (node) {
165
+ while(blocked) {}
166
+ blocked = true;
167
+ var parent = scope.tree.jstree(true).get_node(node.parent);
168
+ var res = scope.tree.jstree(true).create_node(parent, node, 'inside',function() {
169
+ blocked = false;
170
+ });
171
+ if (!res) {
172
+ blocked = false;
173
+ }
174
+ };
175
+
176
+ nodesWatcher.onRemoved = function (node) {
177
+ scope.tree.jstree(true).delete_node(node.id);
178
+ };
179
+
180
+ nodesWatcher.subscribe(scope, function () {
181
+ if (!scope.shouldApply) {
182
+ return true;
183
+ }
184
+ return scope.shouldApply();
185
+ });
186
+
187
+ scope.$watch(getOptions, function () {
188
+ scope.destroy();
189
+ scope.init();
190
+ });
191
+ }
192
+ };
193
+ }
194
+
195
+ //// Angular Code ////
196
+ var mi = angular.module('ngJsTree',[]);
197
+ mi.controller('jsTreeCtrl',jsTreeCtrl);
198
+ mi.directive('jsTree',jsTreeDirective);
199
+
200
+ })(angular);