vinsol_spree_themes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +36 -0
  3. data/.ruby-version +1 -0
  4. data/Appraisals +16 -0
  5. data/Gemfile +9 -0
  6. data/Gemfile.lock +386 -0
  7. data/LICENSE +26 -0
  8. data/README.md +250 -0
  9. data/Rakefile +21 -0
  10. data/app/assets/javascripts/spree/backend/codemirror/codemirror.js +9351 -0
  11. data/app/assets/javascripts/spree/backend/codemirror/css-hint.js +60 -0
  12. data/app/assets/javascripts/spree/backend/codemirror/fullscreen.js +41 -0
  13. data/app/assets/javascripts/spree/backend/codemirror/html-hint.js +348 -0
  14. data/app/assets/javascripts/spree/backend/codemirror/javascript-hint.js +155 -0
  15. data/app/assets/javascripts/spree/backend/codemirror/javascript.js +813 -0
  16. data/app/assets/javascripts/spree/backend/codemirror/markdown.js +796 -0
  17. data/app/assets/javascripts/spree/backend/codemirror/ruby.js +295 -0
  18. data/app/assets/javascripts/spree/backend/codemirror/show-hint.js +438 -0
  19. data/app/assets/javascripts/spree/backend/editor.js +11 -0
  20. data/app/assets/javascripts/spree/backend/jquery.treemenu.js +87 -0
  21. data/app/assets/javascripts/spree/backend/main.js +26 -0
  22. data/app/assets/javascripts/spree/backend/spree_themes.js +2 -0
  23. data/app/assets/javascripts/spree/backend/template.js +51 -0
  24. data/app/assets/javascripts/spree/backend/theme.js +17 -0
  25. data/app/assets/javascripts/spree/frontend/spree_themes.js +2 -0
  26. data/app/assets/stylesheets/spree/backend/codemirror/codemirror.css +340 -0
  27. data/app/assets/stylesheets/spree/backend/codemirror/fullscreen.css +6 -0
  28. data/app/assets/stylesheets/spree/backend/codemirror/show-hint.css +36 -0
  29. data/app/assets/stylesheets/spree/backend/editor.css +7 -0
  30. data/app/assets/stylesheets/spree/backend/jquery.treemenu.css +8 -0
  31. data/app/assets/stylesheets/spree/backend/spree_themes.css +7 -0
  32. data/app/assets/stylesheets/spree/backend/style.css +239 -0
  33. data/app/assets/stylesheets/spree/backend/template_editor.css +39 -0
  34. data/app/assets/stylesheets/spree/frontend/spree_themes.css +6 -0
  35. data/app/assets/stylesheets/spree/frontend/theme_preview.css +9 -0
  36. data/app/controllers/spree/admin/themes_controller.rb +80 -0
  37. data/app/controllers/spree/admin/themes_preview_controller.rb +30 -0
  38. data/app/controllers/spree/admin/themes_templates_controller.rb +60 -0
  39. data/app/controllers/spree/fragment_cache_controller.rb +13 -0
  40. data/app/controllers/spree/store_controller_decorator.rb +37 -0
  41. data/app/helpers/spree/base_helper_decorator.rb +45 -0
  42. data/app/models/spree/theme.rb +153 -0
  43. data/app/models/spree/themes_template.rb +67 -0
  44. data/app/models/spree/themes_template/cache_resolver.rb +37 -0
  45. data/app/overrides/spree/add_preview_bar_to_store.html.rb +6 -0
  46. data/app/overrides/spree/add_theme_meta_details_to_store.html.rb +6 -0
  47. data/app/overrides/spree/admin/add_themes_tab.html.rb +6 -0
  48. data/app/services/assets_precompiler_service.rb +86 -0
  49. data/app/services/file_generator_service.rb +29 -0
  50. data/app/services/template_generator_service.rb +101 -0
  51. data/app/services/zip_file_builder.rb +87 -0
  52. data/app/services/zip_file_extractor.rb +52 -0
  53. data/app/views/spree/admin/shared/_theme_menu_button.html.erb +5 -0
  54. data/app/views/spree/admin/themes/_themes.html.erb +44 -0
  55. data/app/views/spree/admin/themes/_upload.html.erb +17 -0
  56. data/app/views/spree/admin/themes/index.html.erb +50 -0
  57. data/app/views/spree/admin/themes_templates/_editor.html.erb +29 -0
  58. data/app/views/spree/admin/themes_templates/_template.html.erb +12 -0
  59. data/app/views/spree/admin/themes_templates/edit.html.erb +13 -0
  60. data/app/views/spree/admin/themes_templates/edit.js.erb +1 -0
  61. data/app/views/spree/admin/themes_templates/index.html.erb +27 -0
  62. data/app/views/spree/admin/themes_templates/new.html.erb +56 -0
  63. data/app/views/spree/shared/_preview_bar.html.erb +7 -0
  64. data/app/views/spree/shared/_theme_metadata.html.erb +9 -0
  65. data/bin/rails +7 -0
  66. data/config/initializers/assets.rb +24 -0
  67. data/config/initializers/sprockets.rb +85 -0
  68. data/config/locales/en.yml +52 -0
  69. data/config/routes.rb +22 -0
  70. data/db/migrate/20170628072452_vinsol_spree_themes_create_themes_and_themes_templates_table.rb +24 -0
  71. data/gemfiles/spree_3_1.gemfile +8 -0
  72. data/gemfiles/spree_3_2.gemfile +9 -0
  73. data/gemfiles/spree_master.gemfile +9 -0
  74. data/lib/generators/themes/default.zip +0 -0
  75. data/lib/generators/vinsol_spree_themes/install/install_generator.rb +50 -0
  76. data/lib/tasks/sync_templates.rake +47 -0
  77. data/lib/vinsol_spree_themes.rb +3 -0
  78. data/lib/vinsol_spree_themes/engine.rb +22 -0
  79. data/lib/vinsol_spree_themes/factories.rb +6 -0
  80. data/lib/vinsol_spree_themes/version.rb +18 -0
  81. data/spec/spec_helper.rb +93 -0
  82. data/vinsol_spree_themes.gemspec +43 -0
  83. metadata +374 -0
@@ -0,0 +1,295 @@
1
+ // CodeMirror, copyright (c) by Marijn Haverbeke and others
2
+ // Distributed under an MIT license: http://codemirror.net/LICENSE
3
+
4
+ (function(mod) {
5
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
6
+ mod(require("../../lib/codemirror"));
7
+ else if (typeof define == "function" && define.amd) // AMD
8
+ define(["../../lib/codemirror"], mod);
9
+ else // Plain browser env
10
+ mod(CodeMirror);
11
+ })(function(CodeMirror) {
12
+ "use strict";
13
+
14
+ CodeMirror.defineMode("ruby", function(config) {
15
+ function wordObj(words) {
16
+ var o = {};
17
+ for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true;
18
+ return o;
19
+ }
20
+ var keywords = wordObj([
21
+ "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
22
+ "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or",
23
+ "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless",
24
+ "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc",
25
+ "caller", "lambda", "proc", "public", "protected", "private", "require", "load",
26
+ "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__"
27
+ ]);
28
+ var indentWords = wordObj(["def", "class", "case", "for", "while", "until", "module", "then",
29
+ "catch", "loop", "proc", "begin"]);
30
+ var dedentWords = wordObj(["end", "until"]);
31
+ var matching = {"[": "]", "{": "}", "(": ")"};
32
+ var curPunc;
33
+
34
+ function chain(newtok, stream, state) {
35
+ state.tokenize.push(newtok);
36
+ return newtok(stream, state);
37
+ }
38
+
39
+ function tokenBase(stream, state) {
40
+ if (stream.sol() && stream.match("=begin") && stream.eol()) {
41
+ state.tokenize.push(readBlockComment);
42
+ return "comment";
43
+ }
44
+ if (stream.eatSpace()) return null;
45
+ var ch = stream.next(), m;
46
+ if (ch == "`" || ch == "'" || ch == '"') {
47
+ return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state);
48
+ } else if (ch == "/") {
49
+ if (regexpAhead(stream))
50
+ return chain(readQuoted(ch, "string-2", true), stream, state);
51
+ else
52
+ return "operator";
53
+ } else if (ch == "%") {
54
+ var style = "string", embed = true;
55
+ if (stream.eat("s")) style = "atom";
56
+ else if (stream.eat(/[WQ]/)) style = "string";
57
+ else if (stream.eat(/[r]/)) style = "string-2";
58
+ else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; }
59
+ var delim = stream.eat(/[^\w\s=]/);
60
+ if (!delim) return "operator";
61
+ if (matching.propertyIsEnumerable(delim)) delim = matching[delim];
62
+ return chain(readQuoted(delim, style, embed, true), stream, state);
63
+ } else if (ch == "#") {
64
+ stream.skipToEnd();
65
+ return "comment";
66
+ } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) {
67
+ return chain(readHereDoc(m[1]), stream, state);
68
+ } else if (ch == "0") {
69
+ if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/);
70
+ else if (stream.eat("b")) stream.eatWhile(/[01]/);
71
+ else stream.eatWhile(/[0-7]/);
72
+ return "number";
73
+ } else if (/\d/.test(ch)) {
74
+ stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/);
75
+ return "number";
76
+ } else if (ch == "?") {
77
+ while (stream.match(/^\\[CM]-/)) {}
78
+ if (stream.eat("\\")) stream.eatWhile(/\w/);
79
+ else stream.next();
80
+ return "string";
81
+ } else if (ch == ":") {
82
+ if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state);
83
+ if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state);
84
+
85
+ // :> :>> :< :<< are valid symbols
86
+ if (stream.eat(/[\<\>]/)) {
87
+ stream.eat(/[\<\>]/);
88
+ return "atom";
89
+ }
90
+
91
+ // :+ :- :/ :* :| :& :! are valid symbols
92
+ if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) {
93
+ return "atom";
94
+ }
95
+
96
+ // Symbols can't start by a digit
97
+ if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) {
98
+ stream.eatWhile(/[\w$\xa1-\uffff]/);
99
+ // Only one ? ! = is allowed and only as the last character
100
+ stream.eat(/[\?\!\=]/);
101
+ return "atom";
102
+ }
103
+ return "operator";
104
+ } else if (ch == "@" && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) {
105
+ stream.eat("@");
106
+ stream.eatWhile(/[\w\xa1-\uffff]/);
107
+ return "variable-2";
108
+ } else if (ch == "$") {
109
+ if (stream.eat(/[a-zA-Z_]/)) {
110
+ stream.eatWhile(/[\w]/);
111
+ } else if (stream.eat(/\d/)) {
112
+ stream.eat(/\d/);
113
+ } else {
114
+ stream.next(); // Must be a special global like $: or $!
115
+ }
116
+ return "variable-3";
117
+ } else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) {
118
+ stream.eatWhile(/[\w\xa1-\uffff]/);
119
+ stream.eat(/[\?\!]/);
120
+ if (stream.eat(":")) return "atom";
121
+ return "ident";
122
+ } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) {
123
+ curPunc = "|";
124
+ return null;
125
+ } else if (/[\(\)\[\]{}\\;]/.test(ch)) {
126
+ curPunc = ch;
127
+ return null;
128
+ } else if (ch == "-" && stream.eat(">")) {
129
+ return "arrow";
130
+ } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) {
131
+ var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/);
132
+ if (ch == "." && !more) curPunc = ".";
133
+ return "operator";
134
+ } else {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ function regexpAhead(stream) {
140
+ var start = stream.pos, depth = 0, next, found = false, escaped = false
141
+ while ((next = stream.next()) != null) {
142
+ if (!escaped) {
143
+ if ("[{(".indexOf(next) > -1) {
144
+ depth++
145
+ } else if ("]})".indexOf(next) > -1) {
146
+ depth--
147
+ if (depth < 0) break
148
+ } else if (next == "/" && depth == 0) {
149
+ found = true
150
+ break
151
+ }
152
+ escaped = next == "\\"
153
+ } else {
154
+ escaped = false
155
+ }
156
+ }
157
+ stream.backUp(stream.pos - start)
158
+ return found
159
+ }
160
+
161
+ function tokenBaseUntilBrace(depth) {
162
+ if (!depth) depth = 1;
163
+ return function(stream, state) {
164
+ if (stream.peek() == "}") {
165
+ if (depth == 1) {
166
+ state.tokenize.pop();
167
+ return state.tokenize[state.tokenize.length-1](stream, state);
168
+ } else {
169
+ state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1);
170
+ }
171
+ } else if (stream.peek() == "{") {
172
+ state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1);
173
+ }
174
+ return tokenBase(stream, state);
175
+ };
176
+ }
177
+ function tokenBaseOnce() {
178
+ var alreadyCalled = false;
179
+ return function(stream, state) {
180
+ if (alreadyCalled) {
181
+ state.tokenize.pop();
182
+ return state.tokenize[state.tokenize.length-1](stream, state);
183
+ }
184
+ alreadyCalled = true;
185
+ return tokenBase(stream, state);
186
+ };
187
+ }
188
+ function readQuoted(quote, style, embed, unescaped) {
189
+ return function(stream, state) {
190
+ var escaped = false, ch;
191
+
192
+ if (state.context.type === 'read-quoted-paused') {
193
+ state.context = state.context.prev;
194
+ stream.eat("}");
195
+ }
196
+
197
+ while ((ch = stream.next()) != null) {
198
+ if (ch == quote && (unescaped || !escaped)) {
199
+ state.tokenize.pop();
200
+ break;
201
+ }
202
+ if (embed && ch == "#" && !escaped) {
203
+ if (stream.eat("{")) {
204
+ if (quote == "}") {
205
+ state.context = {prev: state.context, type: 'read-quoted-paused'};
206
+ }
207
+ state.tokenize.push(tokenBaseUntilBrace());
208
+ break;
209
+ } else if (/[@\$]/.test(stream.peek())) {
210
+ state.tokenize.push(tokenBaseOnce());
211
+ break;
212
+ }
213
+ }
214
+ escaped = !escaped && ch == "\\";
215
+ }
216
+ return style;
217
+ };
218
+ }
219
+ function readHereDoc(phrase) {
220
+ return function(stream, state) {
221
+ if (stream.match(phrase)) state.tokenize.pop();
222
+ else stream.skipToEnd();
223
+ return "string";
224
+ };
225
+ }
226
+ function readBlockComment(stream, state) {
227
+ if (stream.sol() && stream.match("=end") && stream.eol())
228
+ state.tokenize.pop();
229
+ stream.skipToEnd();
230
+ return "comment";
231
+ }
232
+
233
+ return {
234
+ startState: function() {
235
+ return {tokenize: [tokenBase],
236
+ indented: 0,
237
+ context: {type: "top", indented: -config.indentUnit},
238
+ continuedLine: false,
239
+ lastTok: null,
240
+ varList: false};
241
+ },
242
+
243
+ token: function(stream, state) {
244
+ curPunc = null;
245
+ if (stream.sol()) state.indented = stream.indentation();
246
+ var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype;
247
+ var thisTok = curPunc;
248
+ if (style == "ident") {
249
+ var word = stream.current();
250
+ style = state.lastTok == "." ? "property"
251
+ : keywords.propertyIsEnumerable(stream.current()) ? "keyword"
252
+ : /^[A-Z]/.test(word) ? "tag"
253
+ : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def"
254
+ : "variable";
255
+ if (style == "keyword") {
256
+ thisTok = word;
257
+ if (indentWords.propertyIsEnumerable(word)) kwtype = "indent";
258
+ else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
259
+ else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
260
+ kwtype = "indent";
261
+ else if (word == "do" && state.context.indented < state.indented)
262
+ kwtype = "indent";
263
+ }
264
+ }
265
+ if (curPunc || (style && style != "comment")) state.lastTok = thisTok;
266
+ if (curPunc == "|") state.varList = !state.varList;
267
+
268
+ if (kwtype == "indent" || /[\(\[\{]/.test(curPunc))
269
+ state.context = {prev: state.context, type: curPunc || style, indented: state.indented};
270
+ else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev)
271
+ state.context = state.context.prev;
272
+
273
+ if (stream.eol())
274
+ state.continuedLine = (curPunc == "\\" || style == "operator");
275
+ return style;
276
+ },
277
+
278
+ indent: function(state, textAfter) {
279
+ if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0;
280
+ var firstChar = textAfter && textAfter.charAt(0);
281
+ var ct = state.context;
282
+ var closing = ct.type == matching[firstChar] ||
283
+ ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter);
284
+ return ct.indented + (closing ? 0 : config.indentUnit) +
285
+ (state.continuedLine ? config.indentUnit : 0);
286
+ },
287
+
288
+ electricInput: /^\s*(?:end|rescue|elsif|else|\})$/,
289
+ lineComment: "#"
290
+ };
291
+ });
292
+
293
+ CodeMirror.defineMIME("text/x-ruby", "ruby");
294
+
295
+ });
@@ -0,0 +1,438 @@
1
+ // CodeMirror, copyright (c) by Marijn Haverbeke and others
2
+ // Distributed under an MIT license: http://codemirror.net/LICENSE
3
+
4
+ (function(mod) {
5
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
6
+ mod(require("../../lib/codemirror"));
7
+ else if (typeof define == "function" && define.amd) // AMD
8
+ define(["../../lib/codemirror"], mod);
9
+ else // Plain browser env
10
+ mod(CodeMirror);
11
+ })(function(CodeMirror) {
12
+ "use strict";
13
+
14
+ var HINT_ELEMENT_CLASS = "CodeMirror-hint";
15
+ var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
16
+
17
+ // This is the old interface, kept around for now to stay
18
+ // backwards-compatible.
19
+ CodeMirror.showHint = function(cm, getHints, options) {
20
+ if (!getHints) return cm.showHint(options);
21
+ if (options && options.async) getHints.async = true;
22
+ var newOpts = {hint: getHints};
23
+ if (options) for (var prop in options) newOpts[prop] = options[prop];
24
+ return cm.showHint(newOpts);
25
+ };
26
+
27
+ CodeMirror.defineExtension("showHint", function(options) {
28
+ options = parseOptions(this, this.getCursor("start"), options);
29
+ var selections = this.listSelections()
30
+ if (selections.length > 1) return;
31
+ // By default, don't allow completion when something is selected.
32
+ // A hint function can have a `supportsSelection` property to
33
+ // indicate that it can handle selections.
34
+ if (this.somethingSelected()) {
35
+ if (!options.hint.supportsSelection) return;
36
+ // Don't try with cross-line selections
37
+ for (var i = 0; i < selections.length; i++)
38
+ if (selections[i].head.line != selections[i].anchor.line) return;
39
+ }
40
+
41
+ if (this.state.completionActive) this.state.completionActive.close();
42
+ var completion = this.state.completionActive = new Completion(this, options);
43
+ if (!completion.options.hint) return;
44
+
45
+ CodeMirror.signal(this, "startCompletion", this);
46
+ completion.update(true);
47
+ });
48
+
49
+ function Completion(cm, options) {
50
+ this.cm = cm;
51
+ this.options = options;
52
+ this.widget = null;
53
+ this.debounce = 0;
54
+ this.tick = 0;
55
+ this.startPos = this.cm.getCursor("start");
56
+ this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
57
+
58
+ var self = this;
59
+ cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
60
+ }
61
+
62
+ var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
63
+ return setTimeout(fn, 1000/60);
64
+ };
65
+ var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
66
+
67
+ Completion.prototype = {
68
+ close: function() {
69
+ if (!this.active()) return;
70
+ this.cm.state.completionActive = null;
71
+ this.tick = null;
72
+ this.cm.off("cursorActivity", this.activityFunc);
73
+
74
+ if (this.widget && this.data) CodeMirror.signal(this.data, "close");
75
+ if (this.widget) this.widget.close();
76
+ CodeMirror.signal(this.cm, "endCompletion", this.cm);
77
+ },
78
+
79
+ active: function() {
80
+ return this.cm.state.completionActive == this;
81
+ },
82
+
83
+ pick: function(data, i) {
84
+ var completion = data.list[i];
85
+ if (completion.hint) completion.hint(this.cm, data, completion);
86
+ else this.cm.replaceRange(getText(completion), completion.from || data.from,
87
+ completion.to || data.to, "complete");
88
+ CodeMirror.signal(data, "pick", completion);
89
+ this.close();
90
+ },
91
+
92
+ cursorActivity: function() {
93
+ if (this.debounce) {
94
+ cancelAnimationFrame(this.debounce);
95
+ this.debounce = 0;
96
+ }
97
+
98
+ var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
99
+ if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
100
+ pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
101
+ (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
102
+ this.close();
103
+ } else {
104
+ var self = this;
105
+ this.debounce = requestAnimationFrame(function() {self.update();});
106
+ if (this.widget) this.widget.disable();
107
+ }
108
+ },
109
+
110
+ update: function(first) {
111
+ if (this.tick == null) return
112
+ var self = this, myTick = ++this.tick
113
+ fetchHints(this.options.hint, this.cm, this.options, function(data) {
114
+ if (self.tick == myTick) self.finishUpdate(data, first)
115
+ })
116
+ },
117
+
118
+ finishUpdate: function(data, first) {
119
+ if (this.data) CodeMirror.signal(this.data, "update");
120
+
121
+ var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
122
+ if (this.widget) this.widget.close();
123
+
124
+ if (data && this.data && isNewCompletion(this.data, data)) return;
125
+ this.data = data;
126
+
127
+ if (data && data.list.length) {
128
+ if (picked && data.list.length == 1) {
129
+ this.pick(data, 0);
130
+ } else {
131
+ this.widget = new Widget(this, data);
132
+ CodeMirror.signal(data, "shown");
133
+ }
134
+ }
135
+ }
136
+ };
137
+
138
+ function isNewCompletion(old, nw) {
139
+ var moved = CodeMirror.cmpPos(nw.from, old.from)
140
+ return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch
141
+ }
142
+
143
+ function parseOptions(cm, pos, options) {
144
+ var editor = cm.options.hintOptions;
145
+ var out = {};
146
+ for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
147
+ if (editor) for (var prop in editor)
148
+ if (editor[prop] !== undefined) out[prop] = editor[prop];
149
+ if (options) for (var prop in options)
150
+ if (options[prop] !== undefined) out[prop] = options[prop];
151
+ if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
152
+ return out;
153
+ }
154
+
155
+ function getText(completion) {
156
+ if (typeof completion == "string") return completion;
157
+ else return completion.text;
158
+ }
159
+
160
+ function buildKeyMap(completion, handle) {
161
+ var baseMap = {
162
+ Up: function() {handle.moveFocus(-1);},
163
+ Down: function() {handle.moveFocus(1);},
164
+ PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
165
+ PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
166
+ Home: function() {handle.setFocus(0);},
167
+ End: function() {handle.setFocus(handle.length - 1);},
168
+ Enter: handle.pick,
169
+ Tab: handle.pick,
170
+ Esc: handle.close
171
+ };
172
+ var custom = completion.options.customKeys;
173
+ var ourMap = custom ? {} : baseMap;
174
+ function addBinding(key, val) {
175
+ var bound;
176
+ if (typeof val != "string")
177
+ bound = function(cm) { return val(cm, handle); };
178
+ // This mechanism is deprecated
179
+ else if (baseMap.hasOwnProperty(val))
180
+ bound = baseMap[val];
181
+ else
182
+ bound = val;
183
+ ourMap[key] = bound;
184
+ }
185
+ if (custom)
186
+ for (var key in custom) if (custom.hasOwnProperty(key))
187
+ addBinding(key, custom[key]);
188
+ var extra = completion.options.extraKeys;
189
+ if (extra)
190
+ for (var key in extra) if (extra.hasOwnProperty(key))
191
+ addBinding(key, extra[key]);
192
+ return ourMap;
193
+ }
194
+
195
+ function getHintElement(hintsElement, el) {
196
+ while (el && el != hintsElement) {
197
+ if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
198
+ el = el.parentNode;
199
+ }
200
+ }
201
+
202
+ function Widget(completion, data) {
203
+ this.completion = completion;
204
+ this.data = data;
205
+ this.picked = false;
206
+ var widget = this, cm = completion.cm;
207
+
208
+ var hints = this.hints = document.createElement("ul");
209
+ hints.className = "CodeMirror-hints";
210
+ this.selectedHint = data.selectedHint || 0;
211
+
212
+ var completions = data.list;
213
+ for (var i = 0; i < completions.length; ++i) {
214
+ var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
215
+ var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
216
+ if (cur.className != null) className = cur.className + " " + className;
217
+ elt.className = className;
218
+ if (cur.render) cur.render(elt, data, cur);
219
+ else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
220
+ elt.hintId = i;
221
+ }
222
+
223
+ var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
224
+ var left = pos.left, top = pos.bottom, below = true;
225
+ hints.style.left = left + "px";
226
+ hints.style.top = top + "px";
227
+ // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
228
+ var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
229
+ var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
230
+ (completion.options.container || document.body).appendChild(hints);
231
+ var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
232
+ var scrolls = hints.scrollHeight > hints.clientHeight + 1
233
+ var startScroll = cm.getScrollInfo();
234
+
235
+ if (overlapY > 0) {
236
+ var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
237
+ if (curTop - height > 0) { // Fits above cursor
238
+ hints.style.top = (top = pos.top - height) + "px";
239
+ below = false;
240
+ } else if (height > winH) {
241
+ hints.style.height = (winH - 5) + "px";
242
+ hints.style.top = (top = pos.bottom - box.top) + "px";
243
+ var cursor = cm.getCursor();
244
+ if (data.from.ch != cursor.ch) {
245
+ pos = cm.cursorCoords(cursor);
246
+ hints.style.left = (left = pos.left) + "px";
247
+ box = hints.getBoundingClientRect();
248
+ }
249
+ }
250
+ }
251
+ var overlapX = box.right - winW;
252
+ if (overlapX > 0) {
253
+ if (box.right - box.left > winW) {
254
+ hints.style.width = (winW - 5) + "px";
255
+ overlapX -= (box.right - box.left) - winW;
256
+ }
257
+ hints.style.left = (left = pos.left - overlapX) + "px";
258
+ }
259
+ if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
260
+ node.style.paddingRight = cm.display.nativeBarWidth + "px"
261
+
262
+ cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
263
+ moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
264
+ setFocus: function(n) { widget.changeActive(n); },
265
+ menuSize: function() { return widget.screenAmount(); },
266
+ length: completions.length,
267
+ close: function() { completion.close(); },
268
+ pick: function() { widget.pick(); },
269
+ data: data
270
+ }));
271
+
272
+ if (completion.options.closeOnUnfocus) {
273
+ var closingOnBlur;
274
+ cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
275
+ cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
276
+ }
277
+
278
+ cm.on("scroll", this.onScroll = function() {
279
+ var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
280
+ var newTop = top + startScroll.top - curScroll.top;
281
+ var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
282
+ if (!below) point += hints.offsetHeight;
283
+ if (point <= editor.top || point >= editor.bottom) return completion.close();
284
+ hints.style.top = newTop + "px";
285
+ hints.style.left = (left + startScroll.left - curScroll.left) + "px";
286
+ });
287
+
288
+ CodeMirror.on(hints, "dblclick", function(e) {
289
+ var t = getHintElement(hints, e.target || e.srcElement);
290
+ if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
291
+ });
292
+
293
+ CodeMirror.on(hints, "click", function(e) {
294
+ var t = getHintElement(hints, e.target || e.srcElement);
295
+ if (t && t.hintId != null) {
296
+ widget.changeActive(t.hintId);
297
+ if (completion.options.completeOnSingleClick) widget.pick();
298
+ }
299
+ });
300
+
301
+ CodeMirror.on(hints, "mousedown", function() {
302
+ setTimeout(function(){cm.focus();}, 20);
303
+ });
304
+
305
+ CodeMirror.signal(data, "select", completions[0], hints.firstChild);
306
+ return true;
307
+ }
308
+
309
+ Widget.prototype = {
310
+ close: function() {
311
+ if (this.completion.widget != this) return;
312
+ this.completion.widget = null;
313
+ this.hints.parentNode.removeChild(this.hints);
314
+ this.completion.cm.removeKeyMap(this.keyMap);
315
+
316
+ var cm = this.completion.cm;
317
+ if (this.completion.options.closeOnUnfocus) {
318
+ cm.off("blur", this.onBlur);
319
+ cm.off("focus", this.onFocus);
320
+ }
321
+ cm.off("scroll", this.onScroll);
322
+ },
323
+
324
+ disable: function() {
325
+ this.completion.cm.removeKeyMap(this.keyMap);
326
+ var widget = this;
327
+ this.keyMap = {Enter: function() { widget.picked = true; }};
328
+ this.completion.cm.addKeyMap(this.keyMap);
329
+ },
330
+
331
+ pick: function() {
332
+ this.completion.pick(this.data, this.selectedHint);
333
+ },
334
+
335
+ changeActive: function(i, avoidWrap) {
336
+ if (i >= this.data.list.length)
337
+ i = avoidWrap ? this.data.list.length - 1 : 0;
338
+ else if (i < 0)
339
+ i = avoidWrap ? 0 : this.data.list.length - 1;
340
+ if (this.selectedHint == i) return;
341
+ var node = this.hints.childNodes[this.selectedHint];
342
+ node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
343
+ node = this.hints.childNodes[this.selectedHint = i];
344
+ node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
345
+ if (node.offsetTop < this.hints.scrollTop)
346
+ this.hints.scrollTop = node.offsetTop - 3;
347
+ else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
348
+ this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
349
+ CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
350
+ },
351
+
352
+ screenAmount: function() {
353
+ return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
354
+ }
355
+ };
356
+
357
+ function applicableHelpers(cm, helpers) {
358
+ if (!cm.somethingSelected()) return helpers
359
+ var result = []
360
+ for (var i = 0; i < helpers.length; i++)
361
+ if (helpers[i].supportsSelection) result.push(helpers[i])
362
+ return result
363
+ }
364
+
365
+ function fetchHints(hint, cm, options, callback) {
366
+ if (hint.async) {
367
+ hint(cm, callback, options)
368
+ } else {
369
+ var result = hint(cm, options)
370
+ if (result && result.then) result.then(callback)
371
+ else callback(result)
372
+ }
373
+ }
374
+
375
+ function resolveAutoHints(cm, pos) {
376
+ var helpers = cm.getHelpers(pos, "hint"), words
377
+ if (helpers.length) {
378
+ var resolved = function(cm, callback, options) {
379
+ var app = applicableHelpers(cm, helpers);
380
+ function run(i) {
381
+ if (i == app.length) return callback(null)
382
+ fetchHints(app[i], cm, options, function(result) {
383
+ if (result && result.list.length > 0) callback(result)
384
+ else run(i + 1)
385
+ })
386
+ }
387
+ run(0)
388
+ }
389
+ resolved.async = true
390
+ resolved.supportsSelection = true
391
+ return resolved
392
+ } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
393
+ return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
394
+ } else if (CodeMirror.hint.anyword) {
395
+ return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
396
+ } else {
397
+ return function() {}
398
+ }
399
+ }
400
+
401
+ CodeMirror.registerHelper("hint", "auto", {
402
+ resolve: resolveAutoHints
403
+ });
404
+
405
+ CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
406
+ var cur = cm.getCursor(), token = cm.getTokenAt(cur);
407
+ var to = CodeMirror.Pos(cur.line, token.end);
408
+ if (token.string && /\w/.test(token.string[token.string.length - 1])) {
409
+ var term = token.string, from = CodeMirror.Pos(cur.line, token.start);
410
+ } else {
411
+ var term = "", from = to;
412
+ }
413
+ var found = [];
414
+ for (var i = 0; i < options.words.length; i++) {
415
+ var word = options.words[i];
416
+ if (word.slice(0, term.length) == term)
417
+ found.push(word);
418
+ }
419
+
420
+ if (found.length) return {list: found, from: from, to: to};
421
+ });
422
+
423
+ CodeMirror.commands.autocomplete = CodeMirror.showHint;
424
+
425
+ var defaultOptions = {
426
+ hint: CodeMirror.hint.auto,
427
+ completeSingle: true,
428
+ alignWithWord: true,
429
+ closeCharacters: /[\s()\[\]{};:>,]/,
430
+ closeOnUnfocus: true,
431
+ completeOnSingleClick: true,
432
+ container: null,
433
+ customKeys: null,
434
+ extraKeys: null
435
+ };
436
+
437
+ CodeMirror.defineOption("hintOptions", null);
438
+ });