grandstand 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/MIT-LICENSE +20 -0
- data/README +7 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/app/controllers/admin/galleries_controller.rb +52 -0
- data/app/controllers/admin/images_controller.rb +68 -0
- data/app/controllers/admin/main_controller.rb +36 -0
- data/app/controllers/admin/pages_controller.rb +51 -0
- data/app/controllers/admin/posts_controller.rb +48 -0
- data/app/controllers/admin/sessions_controller.rb +41 -0
- data/app/controllers/admin/templates_controller.rb +6 -0
- data/app/controllers/admin/users_controller.rb +48 -0
- data/app/controllers/galleries_controller.rb +5 -0
- data/app/controllers/pages_controller.rb +5 -0
- data/app/controllers/posts_controller.rb +5 -0
- data/app/helpers/admin/main_helper.rb +31 -0
- data/app/helpers/admin/pages_helper.rb +2 -0
- data/app/helpers/admin/posts_helper.rb +2 -0
- data/app/helpers/admin/sessions_helper.rb +2 -0
- data/app/helpers/admin/templates_helper.rb +2 -0
- data/app/helpers/admin/users_helper.rb +2 -0
- data/app/helpers/pages_helper.rb +2 -0
- data/app/helpers/posts_helper.rb +2 -0
- data/app/helpers/site_helper.rb +2 -0
- data/app/models/gallery.rb +22 -0
- data/app/models/image.rb +61 -0
- data/app/models/page.rb +45 -0
- data/app/models/page_section.rb +6 -0
- data/app/models/post.rb +27 -0
- data/app/models/template.rb +33 -0
- data/app/models/user.rb +68 -0
- data/app/stylesheets/_buttons.less +76 -0
- data/app/stylesheets/_dialogs.less +85 -0
- data/app/stylesheets/application.less +238 -0
- data/app/stylesheets/global.less +435 -0
- data/app/stylesheets/login.less +30 -0
- data/app/stylesheets/wysiwyg.less +96 -0
- data/app/views/admin/galleries/_form.html.erb +11 -0
- data/app/views/admin/galleries/_gallery.html.erb +16 -0
- data/app/views/admin/galleries/_list.html.erb +17 -0
- data/app/views/admin/galleries/delete.html.erb +8 -0
- data/app/views/admin/galleries/edit.html.erb +8 -0
- data/app/views/admin/galleries/editor.html.erb +13 -0
- data/app/views/admin/galleries/editor_with_images.html.erb +19 -0
- data/app/views/admin/galleries/index.html.erb +13 -0
- data/app/views/admin/galleries/new.html.erb +8 -0
- data/app/views/admin/galleries/show.html.erb +15 -0
- data/app/views/admin/images/_form.html.erb +11 -0
- data/app/views/admin/images/delete.html.erb +8 -0
- data/app/views/admin/images/edit.html.erb +8 -0
- data/app/views/admin/images/new.html.erb +8 -0
- data/app/views/admin/images/upload.html.erb +11 -0
- data/app/views/admin/main/index.html.erb +10 -0
- data/app/views/admin/pages/_form.html.erb +33 -0
- data/app/views/admin/pages/_left.html.erb +3 -0
- data/app/views/admin/pages/_row.html.erb +9 -0
- data/app/views/admin/pages/delete.html.erb +8 -0
- data/app/views/admin/pages/edit.html.erb +8 -0
- data/app/views/admin/pages/index.html.erb +20 -0
- data/app/views/admin/pages/new.html.erb +8 -0
- data/app/views/admin/pages/show.html.erb +3 -0
- data/app/views/admin/posts/_form.html.erb +29 -0
- data/app/views/admin/posts/_left.html.erb +3 -0
- data/app/views/admin/posts/_list.html.erb +22 -0
- data/app/views/admin/posts/delete.html.erb +9 -0
- data/app/views/admin/posts/edit.html.erb +10 -0
- data/app/views/admin/posts/index.html.erb +10 -0
- data/app/views/admin/posts/new.html.erb +10 -0
- data/app/views/admin/posts/show.html.erb +4 -0
- data/app/views/admin/sessions/forgot.html.erb +8 -0
- data/app/views/admin/sessions/show.html.erb +12 -0
- data/app/views/admin/shared/_flash.html.erb +3 -0
- data/app/views/admin/users/_form.html.erb +16 -0
- data/app/views/admin/users/_left.html.erb +3 -0
- data/app/views/admin/users/delete.html.erb +10 -0
- data/app/views/admin/users/edit.html.erb +8 -0
- data/app/views/admin/users/index.html.erb +22 -0
- data/app/views/admin/users/new.html.erb +8 -0
- data/app/views/admin/users/show.html.erb +12 -0
- data/app/views/galleries/index.html.erb +0 -0
- data/app/views/galleries/show.html.erb +12 -0
- data/app/views/layouts/admin.html.erb +80 -0
- data/app/views/layouts/admin_login.html.erb +17 -0
- data/app/views/layouts/admin_xhr.html.erb +3 -0
- data/app/views/pages/show.html.erb +8 -0
- data/app/views/posts/show.html.erb +3 -0
- data/app/views/shared/404.html.erb +5 -0
- data/app/views/shared/gallery.html +14 -0
- data/app/views/shared/image.html +1 -0
- data/app/views/shared/page.html +0 -0
- data/app/views/shared/post.html +3 -0
- data/grandstand.gemspec +189 -0
- data/lib/grandstand/application.rb +50 -0
- data/lib/grandstand/controller/development.rb +15 -0
- data/lib/grandstand/controller.rb +104 -0
- data/lib/grandstand/helper.rb +117 -0
- data/lib/grandstand/routes.rb +59 -0
- data/lib/grandstand/session.rb +25 -0
- data/lib/grandstand.rb +27 -0
- data/public/.DS_Store +0 -0
- data/public/admin/.DS_Store +0 -0
- data/public/admin/images/.DS_Store +0 -0
- data/public/admin/images/background-input.gif +0 -0
- data/public/admin/images/background-progress-bar.png +0 -0
- data/public/admin/images/background-progress-complete.gif +0 -0
- data/public/admin/images/background-progress.gif +0 -0
- data/public/admin/images/icons/.DS_Store +0 -0
- data/public/admin/images/icons/add.png +0 -0
- data/public/admin/images/icons/collapse.png +0 -0
- data/public/admin/images/icons/delete.png +0 -0
- data/public/admin/images/icons/edit.png +0 -0
- data/public/admin/images/icons/editor/bold.png +0 -0
- data/public/admin/images/icons/editor/gallery.png +0 -0
- data/public/admin/images/icons/editor/image-center.png +0 -0
- data/public/admin/images/icons/editor/image-left.png +0 -0
- data/public/admin/images/icons/editor/image-right.png +0 -0
- data/public/admin/images/icons/editor/image.png +0 -0
- data/public/admin/images/icons/editor/italic.png +0 -0
- data/public/admin/images/icons/editor/ordered-list.png +0 -0
- data/public/admin/images/icons/editor/quote.png +0 -0
- data/public/admin/images/icons/editor/source.png +0 -0
- data/public/admin/images/icons/editor/strikethrough.png +0 -0
- data/public/admin/images/icons/editor/underline.png +0 -0
- data/public/admin/images/icons/editor/unordered-list.png +0 -0
- data/public/admin/images/icons/error.png +0 -0
- data/public/admin/images/icons/expand.png +0 -0
- data/public/admin/images/icons/galleries.png +0 -0
- data/public/admin/images/icons/gallery.png +0 -0
- data/public/admin/images/icons/image.png +0 -0
- data/public/admin/images/icons/okay.png +0 -0
- data/public/admin/images/icons/pages.png +0 -0
- data/public/admin/images/icons/posts.png +0 -0
- data/public/admin/images/icons/upload.png +0 -0
- data/public/admin/images/icons/users.png +0 -0
- data/public/admin/images/logo.png +0 -0
- data/public/admin/images/spinner-dark.gif +0 -0
- data/public/admin/images/uploader.swf +0 -0
- data/public/admin/javascripts/application.js +231 -0
- data/public/admin/javascripts/jquery.js +404 -0
- data/public/admin/javascripts/mustache.js +324 -0
- data/public/admin/javascripts/selection.js +280 -0
- data/public/admin/javascripts/string.js +264 -0
- data/public/admin/javascripts/wysiwyg.js +335 -0
- data/public/admin/stylesheets/application.css +1 -0
- data/public/admin/stylesheets/global.css +1 -0
- data/public/admin/stylesheets/login.css +1 -0
- data/public/admin/stylesheets/wysiwyg-content.css +20 -0
- data/public/admin/stylesheets/wysiwyg.css +1 -0
- data/vendor/cache/more-0.1.1.gem +0 -0
- 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 "&";
|
250
|
+
case "\\": return "\\\\";
|
251
|
+
case '"': return '\"';
|
252
|
+
case "<": return "<";
|
253
|
+
case ">": return ">";
|
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
|
+
};
|