grandstand 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. data/.gitignore +2 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +7 -0
  4. data/Rakefile +44 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/admin/galleries_controller.rb +52 -0
  7. data/app/controllers/admin/images_controller.rb +68 -0
  8. data/app/controllers/admin/main_controller.rb +36 -0
  9. data/app/controllers/admin/pages_controller.rb +51 -0
  10. data/app/controllers/admin/posts_controller.rb +48 -0
  11. data/app/controllers/admin/sessions_controller.rb +41 -0
  12. data/app/controllers/admin/templates_controller.rb +6 -0
  13. data/app/controllers/admin/users_controller.rb +48 -0
  14. data/app/controllers/galleries_controller.rb +5 -0
  15. data/app/controllers/pages_controller.rb +5 -0
  16. data/app/controllers/posts_controller.rb +5 -0
  17. data/app/helpers/admin/main_helper.rb +31 -0
  18. data/app/helpers/admin/pages_helper.rb +2 -0
  19. data/app/helpers/admin/posts_helper.rb +2 -0
  20. data/app/helpers/admin/sessions_helper.rb +2 -0
  21. data/app/helpers/admin/templates_helper.rb +2 -0
  22. data/app/helpers/admin/users_helper.rb +2 -0
  23. data/app/helpers/pages_helper.rb +2 -0
  24. data/app/helpers/posts_helper.rb +2 -0
  25. data/app/helpers/site_helper.rb +2 -0
  26. data/app/models/gallery.rb +22 -0
  27. data/app/models/image.rb +61 -0
  28. data/app/models/page.rb +45 -0
  29. data/app/models/page_section.rb +6 -0
  30. data/app/models/post.rb +27 -0
  31. data/app/models/template.rb +33 -0
  32. data/app/models/user.rb +68 -0
  33. data/app/stylesheets/_buttons.less +76 -0
  34. data/app/stylesheets/_dialogs.less +85 -0
  35. data/app/stylesheets/application.less +238 -0
  36. data/app/stylesheets/global.less +435 -0
  37. data/app/stylesheets/login.less +30 -0
  38. data/app/stylesheets/wysiwyg.less +96 -0
  39. data/app/views/admin/galleries/_form.html.erb +11 -0
  40. data/app/views/admin/galleries/_gallery.html.erb +16 -0
  41. data/app/views/admin/galleries/_list.html.erb +17 -0
  42. data/app/views/admin/galleries/delete.html.erb +8 -0
  43. data/app/views/admin/galleries/edit.html.erb +8 -0
  44. data/app/views/admin/galleries/editor.html.erb +13 -0
  45. data/app/views/admin/galleries/editor_with_images.html.erb +19 -0
  46. data/app/views/admin/galleries/index.html.erb +13 -0
  47. data/app/views/admin/galleries/new.html.erb +8 -0
  48. data/app/views/admin/galleries/show.html.erb +15 -0
  49. data/app/views/admin/images/_form.html.erb +11 -0
  50. data/app/views/admin/images/delete.html.erb +8 -0
  51. data/app/views/admin/images/edit.html.erb +8 -0
  52. data/app/views/admin/images/new.html.erb +8 -0
  53. data/app/views/admin/images/upload.html.erb +11 -0
  54. data/app/views/admin/main/index.html.erb +10 -0
  55. data/app/views/admin/pages/_form.html.erb +33 -0
  56. data/app/views/admin/pages/_left.html.erb +3 -0
  57. data/app/views/admin/pages/_row.html.erb +9 -0
  58. data/app/views/admin/pages/delete.html.erb +8 -0
  59. data/app/views/admin/pages/edit.html.erb +8 -0
  60. data/app/views/admin/pages/index.html.erb +20 -0
  61. data/app/views/admin/pages/new.html.erb +8 -0
  62. data/app/views/admin/pages/show.html.erb +3 -0
  63. data/app/views/admin/posts/_form.html.erb +29 -0
  64. data/app/views/admin/posts/_left.html.erb +3 -0
  65. data/app/views/admin/posts/_list.html.erb +22 -0
  66. data/app/views/admin/posts/delete.html.erb +9 -0
  67. data/app/views/admin/posts/edit.html.erb +10 -0
  68. data/app/views/admin/posts/index.html.erb +10 -0
  69. data/app/views/admin/posts/new.html.erb +10 -0
  70. data/app/views/admin/posts/show.html.erb +4 -0
  71. data/app/views/admin/sessions/forgot.html.erb +8 -0
  72. data/app/views/admin/sessions/show.html.erb +12 -0
  73. data/app/views/admin/shared/_flash.html.erb +3 -0
  74. data/app/views/admin/users/_form.html.erb +16 -0
  75. data/app/views/admin/users/_left.html.erb +3 -0
  76. data/app/views/admin/users/delete.html.erb +10 -0
  77. data/app/views/admin/users/edit.html.erb +8 -0
  78. data/app/views/admin/users/index.html.erb +22 -0
  79. data/app/views/admin/users/new.html.erb +8 -0
  80. data/app/views/admin/users/show.html.erb +12 -0
  81. data/app/views/galleries/index.html.erb +0 -0
  82. data/app/views/galleries/show.html.erb +12 -0
  83. data/app/views/layouts/admin.html.erb +80 -0
  84. data/app/views/layouts/admin_login.html.erb +17 -0
  85. data/app/views/layouts/admin_xhr.html.erb +3 -0
  86. data/app/views/pages/show.html.erb +8 -0
  87. data/app/views/posts/show.html.erb +3 -0
  88. data/app/views/shared/404.html.erb +5 -0
  89. data/app/views/shared/gallery.html +14 -0
  90. data/app/views/shared/image.html +1 -0
  91. data/app/views/shared/page.html +0 -0
  92. data/app/views/shared/post.html +3 -0
  93. data/grandstand.gemspec +189 -0
  94. data/lib/grandstand/application.rb +50 -0
  95. data/lib/grandstand/controller/development.rb +15 -0
  96. data/lib/grandstand/controller.rb +104 -0
  97. data/lib/grandstand/helper.rb +117 -0
  98. data/lib/grandstand/routes.rb +59 -0
  99. data/lib/grandstand/session.rb +25 -0
  100. data/lib/grandstand.rb +27 -0
  101. data/public/.DS_Store +0 -0
  102. data/public/admin/.DS_Store +0 -0
  103. data/public/admin/images/.DS_Store +0 -0
  104. data/public/admin/images/background-input.gif +0 -0
  105. data/public/admin/images/background-progress-bar.png +0 -0
  106. data/public/admin/images/background-progress-complete.gif +0 -0
  107. data/public/admin/images/background-progress.gif +0 -0
  108. data/public/admin/images/icons/.DS_Store +0 -0
  109. data/public/admin/images/icons/add.png +0 -0
  110. data/public/admin/images/icons/collapse.png +0 -0
  111. data/public/admin/images/icons/delete.png +0 -0
  112. data/public/admin/images/icons/edit.png +0 -0
  113. data/public/admin/images/icons/editor/bold.png +0 -0
  114. data/public/admin/images/icons/editor/gallery.png +0 -0
  115. data/public/admin/images/icons/editor/image-center.png +0 -0
  116. data/public/admin/images/icons/editor/image-left.png +0 -0
  117. data/public/admin/images/icons/editor/image-right.png +0 -0
  118. data/public/admin/images/icons/editor/image.png +0 -0
  119. data/public/admin/images/icons/editor/italic.png +0 -0
  120. data/public/admin/images/icons/editor/ordered-list.png +0 -0
  121. data/public/admin/images/icons/editor/quote.png +0 -0
  122. data/public/admin/images/icons/editor/source.png +0 -0
  123. data/public/admin/images/icons/editor/strikethrough.png +0 -0
  124. data/public/admin/images/icons/editor/underline.png +0 -0
  125. data/public/admin/images/icons/editor/unordered-list.png +0 -0
  126. data/public/admin/images/icons/error.png +0 -0
  127. data/public/admin/images/icons/expand.png +0 -0
  128. data/public/admin/images/icons/galleries.png +0 -0
  129. data/public/admin/images/icons/gallery.png +0 -0
  130. data/public/admin/images/icons/image.png +0 -0
  131. data/public/admin/images/icons/okay.png +0 -0
  132. data/public/admin/images/icons/pages.png +0 -0
  133. data/public/admin/images/icons/posts.png +0 -0
  134. data/public/admin/images/icons/upload.png +0 -0
  135. data/public/admin/images/icons/users.png +0 -0
  136. data/public/admin/images/logo.png +0 -0
  137. data/public/admin/images/spinner-dark.gif +0 -0
  138. data/public/admin/images/uploader.swf +0 -0
  139. data/public/admin/javascripts/application.js +231 -0
  140. data/public/admin/javascripts/jquery.js +404 -0
  141. data/public/admin/javascripts/mustache.js +324 -0
  142. data/public/admin/javascripts/selection.js +280 -0
  143. data/public/admin/javascripts/string.js +264 -0
  144. data/public/admin/javascripts/wysiwyg.js +335 -0
  145. data/public/admin/stylesheets/application.css +1 -0
  146. data/public/admin/stylesheets/global.css +1 -0
  147. data/public/admin/stylesheets/login.css +1 -0
  148. data/public/admin/stylesheets/wysiwyg-content.css +20 -0
  149. data/public/admin/stylesheets/wysiwyg.css +1 -0
  150. data/vendor/cache/more-0.1.1.gem +0 -0
  151. metadata +216 -0
@@ -0,0 +1,324 @@
1
+ /*
2
+ mustache.js — Logic-less templates in JavaScript
3
+
4
+ See http://mustache.github.com/ for more info.
5
+ */
6
+
7
+ var Mustache = function() {
8
+ var Renderer = function() {};
9
+
10
+ Renderer.prototype = {
11
+ otag: "{{",
12
+ ctag: "}}",
13
+ pragmas: {},
14
+ buffer: [],
15
+ pragmas_implemented: {
16
+ "IMPLICIT-ITERATOR": true
17
+ },
18
+ context: {},
19
+
20
+ render: function(template, context, partials, in_recursion) {
21
+ // reset buffer & set context
22
+ if(!in_recursion) {
23
+ this.context = context;
24
+ this.buffer = []; // TODO: make this non-lazy
25
+ }
26
+
27
+ // fail fast
28
+ if(!this.includes("", template)) {
29
+ if(in_recursion) {
30
+ return template;
31
+ } else {
32
+ this.send(template);
33
+ return;
34
+ }
35
+ }
36
+
37
+ template = this.render_pragmas(template);
38
+ var html = this.render_section(template, context, partials);
39
+ if(in_recursion) {
40
+ return this.render_tags(html, context, partials, in_recursion);
41
+ }
42
+
43
+ this.render_tags(html, context, partials, in_recursion);
44
+ },
45
+
46
+ /*
47
+ Sends parsed lines
48
+ */
49
+ send: function(line) {
50
+ if(line != "") {
51
+ this.buffer.push(line);
52
+ }
53
+ },
54
+
55
+ /*
56
+ Looks for %PRAGMAS
57
+ */
58
+ render_pragmas: function(template) {
59
+ // no pragmas
60
+ if(!this.includes("%", template)) {
61
+ return template;
62
+ }
63
+
64
+ var that = this;
65
+ var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
66
+ this.ctag);
67
+ return template.replace(regex, function(match, pragma, options) {
68
+ if(!that.pragmas_implemented[pragma]) {
69
+ throw({message:
70
+ "This implementation of mustache doesn't understand the '" +
71
+ pragma + "' pragma"});
72
+ }
73
+ that.pragmas[pragma] = {};
74
+ if(options) {
75
+ var opts = options.split("=");
76
+ that.pragmas[pragma][opts[0]] = opts[1];
77
+ }
78
+ return "";
79
+ // ignore unknown pragmas silently
80
+ });
81
+ },
82
+
83
+ /*
84
+ Tries to find a partial in the curent scope and render it
85
+ */
86
+ render_partial: function(name, context, partials) {
87
+ name = this.trim(name);
88
+ if(!partials || partials[name] === undefined) {
89
+ throw({message: "unknown_partial '" + name + "'"});
90
+ }
91
+ if(typeof(context[name]) != "object") {
92
+ return this.render(partials[name], context, partials, true);
93
+ }
94
+ return this.render(partials[name], context[name], partials, true);
95
+ },
96
+
97
+ /*
98
+ Renders inverted (^) and normal (#) sections
99
+ */
100
+ render_section: function(template, context, partials) {
101
+ if(!this.includes("#", template) && !this.includes("^", template)) {
102
+ return template;
103
+ }
104
+
105
+ var that = this;
106
+ // CSW - Added "+?" so it finds the tighest bound, not the widest
107
+ var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
108
+ "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
109
+ "\\s*", "mg");
110
+
111
+ // for each {{#foo}}{{/foo}} section do...
112
+ return template.replace(regex, function(match, type, name, content) {
113
+ var value = that.find(name, context);
114
+ if(type == "^") { // inverted section
115
+ if(!value || that.is_array(value) && value.length === 0) {
116
+ // false or empty list, render it
117
+ return that.render(content, context, partials, true);
118
+ } else {
119
+ return "";
120
+ }
121
+ } else if(type == "#") { // normal section
122
+ if(that.is_array(value)) { // Enumerable, Let's loop!
123
+ return that.map(value, function(row) {
124
+ return that.render(content, that.create_context(row),
125
+ partials, true);
126
+ }).join("");
127
+ } else if(that.is_object(value)) { // Object, Use it as subcontext!
128
+ return that.render(content, that.create_context(value),
129
+ partials, true);
130
+ } else if(typeof value === "function") {
131
+ // higher order section
132
+ return value.call(context, content, function(text) {
133
+ return that.render(text, context, partials, true);
134
+ });
135
+ } else if(value) { // boolean section
136
+ return that.render(content, context, partials, true);
137
+ } else {
138
+ return "";
139
+ }
140
+ }
141
+ });
142
+ },
143
+
144
+ /*
145
+ Replace {{foo}} and friends with values from our view
146
+ */
147
+ render_tags: function(template, context, partials, in_recursion) {
148
+ // tit for tat
149
+ var that = this;
150
+
151
+ var new_regex = function() {
152
+ return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
153
+ that.ctag + "+", "g");
154
+ };
155
+
156
+ var regex = new_regex();
157
+ var tag_replace_callback = function(match, operator, name) {
158
+ switch(operator) {
159
+ case "!": // ignore comments
160
+ return "";
161
+ case "=": // set new delimiters, rebuild the replace regexp
162
+ that.set_delimiters(name);
163
+ regex = new_regex();
164
+ return "";
165
+ case ">": // render partial
166
+ return that.render_partial(name, context, partials);
167
+ case "{": // the triple mustache is unescaped
168
+ return that.find(name, context);
169
+ default: // escape the value
170
+ return that.escape(that.find(name, context));
171
+ }
172
+ };
173
+ var lines = template.split("\n");
174
+ for(var i = 0; i < lines.length; i++) {
175
+ lines[i] = lines[i].replace(regex, tag_replace_callback, this);
176
+ if(!in_recursion) {
177
+ this.send(lines[i]);
178
+ }
179
+ }
180
+
181
+ if(in_recursion) {
182
+ return lines.join("\n");
183
+ }
184
+ },
185
+
186
+ set_delimiters: function(delimiters) {
187
+ var dels = delimiters.split(" ");
188
+ this.otag = this.escape_regex(dels[0]);
189
+ this.ctag = this.escape_regex(dels[1]);
190
+ },
191
+
192
+ escape_regex: function(text) {
193
+ // thank you Simon Willison
194
+ if(!arguments.callee.sRE) {
195
+ var specials = [
196
+ '/', '.', '*', '+', '?', '|',
197
+ '(', ')', '[', ']', '{', '}', '\\'
198
+ ];
199
+ arguments.callee.sRE = new RegExp(
200
+ '(\\' + specials.join('|\\') + ')', 'g'
201
+ );
202
+ }
203
+ return text.replace(arguments.callee.sRE, '\\$1');
204
+ },
205
+
206
+ /*
207
+ find `name` in current `context`. That is find me a value
208
+ from the view object
209
+ */
210
+ find: function(name, context) {
211
+ name = this.trim(name);
212
+
213
+ // Checks whether a value is thruthy or false or 0
214
+ function is_kinda_truthy(bool) {
215
+ return bool === false || bool === 0 || bool;
216
+ }
217
+
218
+ var value;
219
+ if(is_kinda_truthy(context[name])) {
220
+ value = context[name];
221
+ } else if(is_kinda_truthy(this.context[name])) {
222
+ value = this.context[name];
223
+ }
224
+
225
+ if(typeof value === "function") {
226
+ return value.apply(context);
227
+ }
228
+ if(value !== undefined) {
229
+ return value;
230
+ }
231
+ // silently ignore unkown variables
232
+ return "";
233
+ },
234
+
235
+ // Utility methods
236
+
237
+ /* includes tag */
238
+ includes: function(needle, haystack) {
239
+ return haystack.indexOf(this.otag + needle) != -1;
240
+ },
241
+
242
+ /*
243
+ Does away with nasty characters
244
+ */
245
+ escape: function(s) {
246
+ s = String(s === null ? "" : s);
247
+ return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
248
+ switch(s) {
249
+ case "&": return "&amp;";
250
+ case "\\": return "\\\\";
251
+ case '"': return '\"';
252
+ case "<": return "&lt;";
253
+ case ">": return "&gt;";
254
+ default: return s;
255
+ }
256
+ });
257
+ },
258
+
259
+ // by @langalex, support for arrays of strings
260
+ create_context: function(_context) {
261
+ if(this.is_object(_context)) {
262
+ return _context;
263
+ } else {
264
+ var iterator = ".";
265
+ if(this.pragmas["IMPLICIT-ITERATOR"]) {
266
+ iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
267
+ }
268
+ var ctx = {};
269
+ ctx[iterator] = _context;
270
+ return ctx;
271
+ }
272
+ },
273
+
274
+ is_object: function(a) {
275
+ return a && typeof a == "object";
276
+ },
277
+
278
+ is_array: function(a) {
279
+ return Object.prototype.toString.call(a) === '[object Array]';
280
+ },
281
+
282
+ /*
283
+ Gets rid of leading and trailing whitespace
284
+ */
285
+ trim: function(s) {
286
+ return s.replace(/^\s*|\s*$/g, "");
287
+ },
288
+
289
+ /*
290
+ Why, why, why? Because IE. Cry, cry cry.
291
+ */
292
+ map: function(array, fn) {
293
+ if (typeof array.map == "function") {
294
+ return array.map(fn);
295
+ } else {
296
+ var r = [];
297
+ var l = array.length;
298
+ for(var i = 0; i < l; i++) {
299
+ r.push(fn(array[i]));
300
+ }
301
+ return r;
302
+ }
303
+ }
304
+ };
305
+
306
+ return({
307
+ name: "mustache.js",
308
+ version: "0.3.0-dev",
309
+
310
+ /*
311
+ Turns a template and view into HTML
312
+ */
313
+ to_html: function(template, view, partials, send_fun) {
314
+ var renderer = new Renderer();
315
+ if(send_fun) {
316
+ renderer.send = send_fun;
317
+ }
318
+ renderer.render(template, view, partials);
319
+ if(!send_fun) {
320
+ return renderer.buffer.join("\n");
321
+ }
322
+ }
323
+ });
324
+ }();
@@ -0,0 +1,280 @@
1
+ /*
2
+ * Selection.js
3
+ * Copyright (c) 2010 Flip Sasser, All Rights Reserved
4
+ * Use to manipulate a text selection object using jQuery. Fun!
5
+ *
6
+ */
7
+
8
+ require('string');
9
+
10
+ $.extend($.expr[':'], {
11
+ block: function(a) {
12
+ return $(a).css('display') === 'block';
13
+ },
14
+ textnode: function(a) {
15
+ return a.nodeType == 3;
16
+ }
17
+ });
18
+
19
+ jQuery.fn.selection = function(rootSelector) {
20
+ return new Selection(this[0], rootSelector);
21
+ };
22
+
23
+ // The selection class will provide some basic range finding methods, such as:
24
+ // * Find text before and after the selection, inside of current block
25
+ // * Move the caret to a block if it's in a Textnode directly under body
26
+ // The selection class can be used to move after() to a new adjacent
27
+ // block without much overhead or trouble. It can also be used to wrap,
28
+ // unwrap, and detect wrapping tags in, HTML content. Example:
29
+ //
30
+ // var selection = $(window).selection();
31
+ // var before = selection.before();
32
+ // var after = selection.after();
33
+ // // Splits the `before` and `after` into
34
+ // // two new blocks; the default block type is
35
+ // // paragraph but that can be changed:
36
+ // var newParagraph = selection.split('p');
37
+ // selection.wrap('strong');
38
+ // selection.unwrap('strong');
39
+ // selection.wrappedIn('strong'); //=> true if the selection is inside <strong>
40
+ // selection.insert('<img alt="Image!" src="/images/foo.gif" />');
41
+
42
+ var Selection = function(element, rootSelector) {
43
+ this.window = element;
44
+ this.document = element.document;
45
+ this.rootSelector = rootSelector || 'body';
46
+ this.root = $(this.document).find(this.rootSelector + ':first');
47
+ };
48
+
49
+ Selection.prototype = {
50
+ // `after` and `before` return the strings before and after the current selection,
51
+ // inside of the current block (or an automatically-created paragraph block).
52
+ after: function() {
53
+ return this.afterRange().toString();
54
+ },
55
+ afterAll: function() {
56
+ var range = this.range();
57
+ var afterAllRange = this.document.createRange();
58
+ afterAllRange.selectNodeContents(this.root[0]);
59
+ afterAllRange.setStart(range.endContainer, range.endOffset);
60
+ return afterAllRange.cloneContents();
61
+ },
62
+ before: function() {
63
+ return this.beforeRange().toString();
64
+ },
65
+ beforeAll: function() {
66
+ var range = this.range();
67
+ var beforeAllRange = this.document.createRange();
68
+ beforeAllRange.setStart(this.root[0], 0);
69
+ beforeAllRange.setEnd(range.startContainer, range.startOffset);
70
+ return beforeAllRange.cloneContents();
71
+ },
72
+ // Inserts some content into the selection. Really, it just replaces the
73
+ // current selection with the content. Coolness.
74
+ insert: function(content) {
75
+ this.replace(content);
76
+ },
77
+ normalize: function() {
78
+ if (this.startBlock().length > 0) {
79
+ this.startBlock()[0].normalize();
80
+ }
81
+ if (this.endBlock().length > 0) {
82
+ this.endBlock()[0].normalize();
83
+ }
84
+ },
85
+ // Returns the before or after range of the current selection, scoped to the
86
+ // containing block. If no containing block is found, it automatically
87
+ // creates one and set the range inside of it.
88
+ range: function(name) {
89
+ return this._data('range', function() {
90
+ var range;
91
+ if (this.selection.getRangeAt) {
92
+ range = this.selection.getRangeAt(0);
93
+ } else if (this.document.selection) {
94
+ range = this.document.createRange();
95
+ range.setStart(this.selection.anchorNode, this.selection.anchorOffset);
96
+ range.setEnd(this.selection.focusNode, this.selection.focusOffset);
97
+ }
98
+ return range;
99
+ });
100
+ },
101
+ // Replaces the selection with the passed-in content. Primarily used internally
102
+ // for `insert` and `wrap`.
103
+ replace: function(content) {
104
+ this.range().deleteContents();
105
+ var startNode = this.startNode();
106
+ startNode.append(content);
107
+ this.select(startNode);
108
+ },
109
+ // Sets the caret position inside the first element that matches the selector.
110
+ // You can also pass in an element and an offset if you're feeling extra-aggressive,
111
+ // but be forewarned: the element MUST appear inside the `root` element of the
112
+ // selection, or you can go to hell, sir.
113
+ select: function(selector, offset) {
114
+ this.root.focus();
115
+ var element;
116
+ if (typeof(selector) == 'object') {
117
+ element = $(selector);
118
+ } else {
119
+ element = this.root.find(selector + ':first');
120
+ }
121
+ if (!offset) {
122
+ offset = 0;
123
+ }
124
+ element = element[0];
125
+ if (element) {
126
+ if (!this.selection) {
127
+ this.selection = this.window.getSelection ? this.window.getSelection() : this.document.selection;
128
+ }
129
+ var range = this.document.createRange();
130
+ range.selectNodeContents(element);
131
+ range.collapse(true);
132
+ this.selection.removeAllRanges();
133
+ this.selection.addRange(range);
134
+ } else {
135
+ this.root.focus();
136
+ }
137
+ },
138
+ split: function(tagname) {
139
+ // Default to a new paragraph
140
+ if (!tagname) {
141
+ tagname = 'p';
142
+ }
143
+ tagname = tagname.toLowerCase();
144
+ // Build whatever comes after the current node, cutting the contents out
145
+ var afterNode = $('<' + tagname + '></' + tagname + '>');
146
+ var after = this.afterRange();
147
+ afterNode.html(after.cloneContents());
148
+ afterNode.append('<br />');
149
+ // Add the new node after the current block - and then, and this is important,
150
+ // SELECT the top of that block
151
+ var endBlock = this.endBlock();
152
+ endBlock.after(afterNode);
153
+ var startBlock = this.startBlock();
154
+ if (startBlock == endBlock && startBlock.text() == '') {
155
+ afterNode = $('<br />');
156
+ startBlock.append(afterNode);
157
+ }
158
+ this.select(afterNode);
159
+ this.range().deleteContents();
160
+ after.deleteContents();
161
+ startBlock[0].normalize();
162
+ endBlock[0].normalize();
163
+ },
164
+ afterRange: function() {
165
+ return this._data('afterRange', function() {
166
+ var endNode = this.endNode();
167
+
168
+ var afterRange = this.document.createRange();
169
+ afterRange.setStart(endNode[0], this.data['endOffset']);
170
+ afterRange.setEnd(endNode[0], (endNode[0].wholeText || endNode.html()).length);
171
+
172
+ return afterRange;
173
+ });
174
+ },
175
+ beforeRange: function() {
176
+ return this._data('beforeRange', function() {
177
+ var startNode = this.startNode();
178
+
179
+ var beforeRange = this.document.createRange();
180
+ beforeRange.setStart(startNode[0], 0);
181
+ beforeRange.setEnd(startNode[0], this.data['startOffset']);
182
+
183
+ return beforeRange;
184
+ });
185
+ },
186
+ endNode: function() {
187
+ return this._data('endNode', function() {
188
+ var beforeRange = this.document.createRange();
189
+ beforeRange.setStart(this.selection.anchorNode, this.selection.anchorOffset);
190
+ beforeRange.collapse(true);
191
+
192
+ var afterRange = this.document.createRange();
193
+ afterRange.setStart(this.selection.focusNode, this.selection.focusOffset);
194
+ afterRange.collapse(true);
195
+
196
+ var ltr = beforeRange.compareBoundaryPoints(beforeRange.START_TO_END, afterRange) < 0;
197
+
198
+ var endNode = $(ltr ? this.selection.focusNode : this.selection.anchorNode);
199
+ this.data['startNode'] = $(ltr ? this.selection.anchorNode : this.selection.focusNode);
200
+ this.data['startOffset'] = ltr ? this.selection.anchorOffset : this.selection.focusOffset;
201
+ this.data['endOffset'] = ltr ? this.selection.focusOffset : this.selection.anchorOffset;
202
+
203
+ return endNode;
204
+
205
+ });
206
+ },
207
+ endBlock: function() {
208
+ return this._data('endBlock', function() {
209
+ var endNode = this.endNode();
210
+ var endBlock = endNode.parents(':block:not(body, html, ' + this.rootSelector + '):first') || endNode;
211
+ if (endBlock.length > 0) {
212
+ endBlock[0].normalize();
213
+ }
214
+ return endBlock;
215
+ });
216
+ },
217
+ startBlock: function() {
218
+ return this._data('startBlock', function() {
219
+ var startNode = this.startNode();
220
+ return startNode.parents(':block:not(body, html):first') || startNode;
221
+ });
222
+ },
223
+ startNode: function() {
224
+ return this._data('startNode', function() {
225
+ var beforeRange = this.document.createRange();
226
+ beforeRange.setStart(this.selection.anchorNode, this.selection.anchorOffset);
227
+ beforeRange.collapse(true);
228
+
229
+ var afterRange = this.document.createRange();
230
+ afterRange.setStart(this.selection.focusNode, this.selection.focusOffset);
231
+ afterRange.collapse(true);
232
+
233
+ var ltr = beforeRange.compareBoundaryPoints(beforeRange.START_TO_END, afterRange) < 0;
234
+ var startNode = $(ltr ? this.selection.anchorNode : this.selection.focusNode);
235
+ this.data['startOffset'] = ltr ? this.selection.anchorOffset : this.selection.focusOffset;
236
+ this.data['endNode'] = $(ltr ? this.selection.focusNode : this.selection.anchorNode);
237
+ this.data['endOffset'] = ltr ? this.selection.focusOffset : this.selection.anchorOffset;
238
+
239
+ return startNode;
240
+ });
241
+ },
242
+ unwrap: function(tagname) {
243
+ var tags = this.wrappedIn(tagname);
244
+ if (tags) {
245
+
246
+ }
247
+ },
248
+ wrap: function(tagname) {
249
+ // Nice, clean-looking HTML.
250
+ tagname = tagname.toLowerCase();
251
+ var wrapper = this.document.createElement(tagname);
252
+ this.range().surroundContents(wrapper);
253
+ },
254
+ // Returns a boolean for whether or not the selection (or caret position)
255
+ // is inside of a certain tag. Useful for activating / deactivating button
256
+ // items as the user navigates.
257
+ wrappedIn: function(selector) {
258
+ var startNode = this.startNode();
259
+ if (startNode.is(selector)) {
260
+ return startNode;
261
+ } else {
262
+ var parents = startNode.parents(selector);
263
+ if (parents.length === 0) {
264
+ return false;
265
+ } else {
266
+ return parents;
267
+ }
268
+ }
269
+ },
270
+ _data: function(name, block) {
271
+ if (!this.data) {
272
+ this.data = {};
273
+ }
274
+ if (!this.data[name]) {
275
+ this.selection = this.window.getSelection ? this.window.getSelection() : this.document.selection;
276
+ this.data[name] = block.call(this);
277
+ }
278
+ return this.data[name];
279
+ }
280
+ };