##// END OF EJS Templates
codemirror: move some code outside of helpers for reuse
ergo -
r874:6903ccc9 default
parent child Browse files
Show More
@@ -1,530 +1,530 b''
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * Code Mirror
20 * Code Mirror
21 */
21 */
22 // global code-mirror logger;, to enable run
22 // global code-mirror logger;, to enable run
23 // Logger.get('CodeMirror').setLevel(Logger.DEBUG)
23 // Logger.get('CodeMirror').setLevel(Logger.DEBUG)
24
24
25 cmLog = Logger.get('CodeMirror');
25 cmLog = Logger.get('CodeMirror');
26 cmLog.setLevel(Logger.OFF);
26 cmLog.setLevel(Logger.OFF);
27
27
28
28
29 //global cache for inline forms
29 //global cache for inline forms
30 var userHintsCache = {};
30 var userHintsCache = {};
31
31
32 // global timer, used to cancel async loading
33 var CodeMirrorLoadUserHintTimer;
34
35 var escapeRegExChars = function(value) {
36 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
37 };
38
39 /**
40 * Load hints from external source returns an array of objects in a format
41 * that hinting lib requires
42 * @returns {Array}
43 */
44 var CodeMirrorLoadUserHints = function(query, triggerHints) {
45 cmLog.debug('Loading mentions users via AJAX');
46 var _users = [];
47 $.ajax({
48 type: 'GET',
49 data: {query: query},
50 url: pyroutes.url('user_autocomplete_data'),
51 headers: {'X-PARTIAL-XHR': true},
52 async: true
53 })
54 .done(function(data) {
55 var tmpl = '<img class="gravatar" src="{0}"/>{1}';
56 $.each(data.suggestions, function(i) {
57 var userObj = data.suggestions[i];
58
59 if (userObj.username !== "default") {
60 _users.push({
61 text: userObj.username + " ",
62 org_text: userObj.username,
63 displayText: userObj.value_display, // search that field
64 // internal caches
65 _icon_link: userObj.icon_link,
66 _text: userObj.value_display,
67
68 render: function(elt, data, completion) {
69 var el = document.createElement('div');
70 el.className = "CodeMirror-hint-entry";
71 el.innerHTML = tmpl.format(
72 completion._icon_link, completion._text);
73 elt.appendChild(el);
74 }
75 });
76 }
77 });
78 cmLog.debug('Mention users loaded');
79 // set to global cache
80 userHintsCache[query] = _users;
81 triggerHints(userHintsCache[query]);
82 })
83 .fail(function(data, textStatus, xhr) {
84 alert("error processing request: " + textStatus);
85 });
86 };
87
88 /**
89 * filters the results based on the current context
90 * @param users
91 * @param context
92 * @returns {Array}
93 */
94 var CodeMirrorFilterUsers = function(users, context) {
95 var MAX_LIMIT = 10;
96 var filtered_users = [];
97 var curWord = context.string;
98
99 cmLog.debug('Filtering users based on query:', curWord);
100 $.each(users, function(i) {
101 var match = users[i];
102 var searchText = match.displayText;
103
104 if (!curWord ||
105 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
106 // reset state
107 match._text = match.displayText;
108 if (curWord) {
109 // do highlighting
110 var pattern = '(' + escapeRegExChars(curWord) + ')';
111 match._text = searchText.replace(
112 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
113 }
114
115 filtered_users.push(match);
116 }
117 // to not return to many results, use limit of filtered results
118 if (filtered_users.length > MAX_LIMIT) {
119 return false;
120 }
121 });
122
123 return filtered_users;
124 };
125
126 var CodeMirrorMentionHint = function(editor, callback, options) {
127 var cur = editor.getCursor();
128 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
129
130 // match on @ +1char
131 var tokenMatch = new RegExp(
132 '(^@| @)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*)$').exec(curLine);
133
134 var tokenStr = '';
135 if (tokenMatch !== null && tokenMatch.length > 0){
136 tokenStr = tokenMatch[0].strip();
137 } else {
138 // skip if we didn't match our token
139 return;
140 }
141
142 var context = {
143 start: (cur.ch - tokenStr.length) + 1,
144 end: cur.ch,
145 string: tokenStr.slice(1),
146 type: null
147 };
148
149 // case when we put the @sign in fron of a string,
150 // eg <@ we put it here>sometext then we need to prepend to text
151 if (context.end > cur.ch) {
152 context.start = context.start + 1; // we add to the @ sign
153 context.end = cur.ch; // don't eat front part just append
154 context.string = context.string.slice(1, cur.ch - context.start);
155 }
156
157 cmLog.debug('Mention context', context);
158
159 var triggerHints = function(userHints){
160 return callback({
161 list: CodeMirrorFilterUsers(userHints, context),
162 from: CodeMirror.Pos(cur.line, context.start),
163 to: CodeMirror.Pos(cur.line, context.end)
164 });
165 };
166
167 var queryBasedHintsCache = undefined;
168 // if we have something in the cache, try to fetch the query based cache
169 if (userHintsCache !== {}){
170 queryBasedHintsCache = userHintsCache[context.string];
171 }
172
173 if (queryBasedHintsCache !== undefined) {
174 cmLog.debug('Users loaded from cache');
175 triggerHints(queryBasedHintsCache);
176 } else {
177 // this takes care for async loading, and then displaying results
178 // and also propagates the userHintsCache
179 window.clearTimeout(CodeMirrorLoadUserHintTimer);
180 CodeMirrorLoadUserHintTimer = setTimeout(function() {
181 CodeMirrorLoadUserHints(context.string, triggerHints);
182 }, 300);
183 }
184 };
185
186 var CodeMirrorCompleteAfter = function(cm, pred) {
187 var options = {
188 completeSingle: false,
189 async: true,
190 closeOnUnfocus: true
191 };
192 var cur = cm.getCursor();
193 setTimeout(function() {
194 if (!cm.state.completionActive) {
195 cmLog.debug('Trigger mentions hinting');
196 CodeMirror.showHint(cm, CodeMirror.hint.mentions, options);
197 }
198 }, 100);
199
200 // tell CodeMirror we didn't handle the key
201 // trick to trigger on a char but still complete it
202 return CodeMirror.Pass;
203 };
32
204
33 var initCodeMirror = function(textAreadId, resetUrl, focus, options) {
205 var initCodeMirror = function(textAreadId, resetUrl, focus, options) {
34 var ta = $('#' + textAreadId).get(0);
206 var ta = $('#' + textAreadId).get(0);
35 if (focus === undefined) {
207 if (focus === undefined) {
36 focus = true;
208 focus = true;
37 }
209 }
38
210
39 // default options
211 // default options
40 var codeMirrorOptions = {
212 var codeMirrorOptions = {
41 mode: "null",
213 mode: "null",
42 lineNumbers: true,
214 lineNumbers: true,
43 indentUnit: 4,
215 indentUnit: 4,
44 autofocus: focus
216 autofocus: focus
45 };
217 };
46
218
47 if (options !== undefined) {
219 if (options !== undefined) {
48 // extend with custom options
220 // extend with custom options
49 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
221 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
50 }
222 }
51
223
52 var myCodeMirror = CodeMirror.fromTextArea(ta, codeMirrorOptions);
224 var myCodeMirror = CodeMirror.fromTextArea(ta, codeMirrorOptions);
53
225
54 $('#reset').on('click', function(e) {
226 $('#reset').on('click', function(e) {
55 window.location = resetUrl;
227 window.location = resetUrl;
56 });
228 });
57
229
58 return myCodeMirror;
230 return myCodeMirror;
59 };
231 };
60
232
61 var initCommentBoxCodeMirror = function(textAreaId, triggerActions){
233 var initCommentBoxCodeMirror = function(textAreaId, triggerActions){
62 var initialHeight = 100;
234 var initialHeight = 100;
63
235
64 // global timer, used to cancel async loading
65 var loadUserHintTimer;
66
67 if (typeof userHintsCache === "undefined") {
236 if (typeof userHintsCache === "undefined") {
68 userHintsCache = {};
237 userHintsCache = {};
69 cmLog.debug('Init empty cache for mentions');
238 cmLog.debug('Init empty cache for mentions');
70 }
239 }
71 if (!$(textAreaId).get(0)) {
240 if (!$(textAreaId).get(0)) {
72 cmLog.debug('Element for textarea not found', textAreaId);
241 cmLog.debug('Element for textarea not found', textAreaId);
73 return;
242 return;
74 }
243 }
75 var escapeRegExChars = function(value) {
76 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
77 };
78 /**
79 * Load hints from external source returns an array of objects in a format
80 * that hinting lib requires
81 * @returns {Array}
82 */
83 var loadUserHints = function(query, triggerHints) {
84 cmLog.debug('Loading mentions users via AJAX');
85 var _users = [];
86 $.ajax({
87 type: 'GET',
88 data: {query: query},
89 url: pyroutes.url('user_autocomplete_data'),
90 headers: {'X-PARTIAL-XHR': true},
91 async: true
92 })
93 .done(function(data) {
94 var tmpl = '<img class="gravatar" src="{0}"/>{1}';
95 $.each(data.suggestions, function(i) {
96 var userObj = data.suggestions[i];
97
98 if (userObj.username !== "default") {
99 _users.push({
100 text: userObj.username + " ",
101 org_text: userObj.username,
102 displayText: userObj.value_display, // search that field
103 // internal caches
104 _icon_link: userObj.icon_link,
105 _text: userObj.value_display,
106
107 render: function(elt, data, completion) {
108 var el = document.createElement('div');
109 el.className = "CodeMirror-hint-entry";
110 el.innerHTML = tmpl.format(
111 completion._icon_link, completion._text);
112 elt.appendChild(el);
113 }
114 });
115 }
116 });
117 cmLog.debug('Mention users loaded');
118 // set to global cache
119 userHintsCache[query] = _users;
120 triggerHints(userHintsCache[query]);
121 })
122 .fail(function(data, textStatus, xhr) {
123 alert("error processing request: " + textStatus);
124 });
125 };
126
127 /**
128 * filters the results based on the current context
129 * @param users
130 * @param context
131 * @returns {Array}
132 */
133 var filterUsers = function(users, context) {
134 var MAX_LIMIT = 10;
135 var filtered_users = [];
136 var curWord = context.string;
137
138 cmLog.debug('Filtering users based on query:', curWord);
139 $.each(users, function(i) {
140 var match = users[i];
141 var searchText = match.displayText;
142
143 if (!curWord ||
144 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
145 // reset state
146 match._text = match.displayText;
147 if (curWord) {
148 // do highlighting
149 var pattern = '(' + escapeRegExChars(curWord) + ')';
150 match._text = searchText.replace(
151 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
152 }
153
154 filtered_users.push(match);
155 }
156 // to not return to many results, use limit of filtered results
157 if (filtered_users.length > MAX_LIMIT) {
158 return false;
159 }
160 });
161
162 return filtered_users;
163 };
164
165 /**
244 /**
166 * Filter action based on typed in text
245 * Filter action based on typed in text
167 * @param actions
246 * @param actions
168 * @param context
247 * @param context
169 * @returns {Array}
248 * @returns {Array}
170 */
249 */
171
250
172 var filterActions = function(actions, context){
251 var filterActions = function(actions, context){
173 var MAX_LIMIT = 10;
252 var MAX_LIMIT = 10;
174 var filtered_actions= [];
253 var filtered_actions= [];
175 var curWord = context.string;
254 var curWord = context.string;
176
255
177 cmLog.debug('Filtering actions based on query:', curWord);
256 cmLog.debug('Filtering actions based on query:', curWord);
178 $.each(actions, function(i) {
257 $.each(actions, function(i) {
179 var match = actions[i];
258 var match = actions[i];
180 var searchText = match.displayText;
259 var searchText = match.displayText;
181
260
182 if (!curWord ||
261 if (!curWord ||
183 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
262 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
184 // reset state
263 // reset state
185 match._text = match.displayText;
264 match._text = match.displayText;
186 if (curWord) {
265 if (curWord) {
187 // do highlighting
266 // do highlighting
188 var pattern = '(' + escapeRegExChars(curWord) + ')';
267 var pattern = '(' + escapeRegExChars(curWord) + ')';
189 match._text = searchText.replace(
268 match._text = searchText.replace(
190 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
269 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
191 }
270 }
192
271
193 filtered_actions.push(match);
272 filtered_actions.push(match);
194 }
273 }
195 // to not return to many results, use limit of filtered results
274 // to not return to many results, use limit of filtered results
196 if (filtered_actions.length > MAX_LIMIT) {
275 if (filtered_actions.length > MAX_LIMIT) {
197 return false;
276 return false;
198 }
277 }
199 });
278 });
200 return filtered_actions;
279 return filtered_actions;
201 };
280 };
202
281
203 var completeAfter = function(cm, pred) {
204 var options = {
205 completeSingle: false,
206 async: true,
207 closeOnUnfocus: true
208 };
209 var cur = cm.getCursor();
210 setTimeout(function() {
211 if (!cm.state.completionActive) {
212 cmLog.debug('Trigger mentions hinting');
213 CodeMirror.showHint(cm, CodeMirror.hint.mentions, options);
214 }
215 }, 100);
216
217 // tell CodeMirror we didn't handle the key
218 // trick to trigger on a char but still complete it
219 return CodeMirror.Pass;
220 };
221
222 var submitForm = function(cm, pred) {
282 var submitForm = function(cm, pred) {
223 $(cm.display.input.textarea.form).submit();
283 $(cm.display.input.textarea.form).submit();
224 return CodeMirror.Pass;
284 return CodeMirror.Pass;
225 };
285 };
226
286
227 var completeActions = function(cm, pred) {
287 var completeActions = function(cm, pred) {
228 var cur = cm.getCursor();
288 var cur = cm.getCursor();
229 var options = {
289 var options = {
230 closeOnUnfocus: true
290 closeOnUnfocus: true
231 };
291 };
232 setTimeout(function() {
292 setTimeout(function() {
233 if (!cm.state.completionActive) {
293 if (!cm.state.completionActive) {
234 cmLog.debug('Trigger actions hinting');
294 cmLog.debug('Trigger actions hinting');
235 CodeMirror.showHint(cm, CodeMirror.hint.actions, options);
295 CodeMirror.showHint(cm, CodeMirror.hint.actions, options);
236 }
296 }
237 }, 100);
297 }, 100);
238 };
298 };
239
299
240 var extraKeys = {
300 var extraKeys = {
241 "'@'": completeAfter,
301 "'@'": CodeMirrorCompleteAfter,
242 Tab: function(cm) {
302 Tab: function(cm) {
243 // space indent instead of TABS
303 // space indent instead of TABS
244 var spaces = new Array(cm.getOption("indentUnit") + 1).join(" ");
304 var spaces = new Array(cm.getOption("indentUnit") + 1).join(" ");
245 cm.replaceSelection(spaces);
305 cm.replaceSelection(spaces);
246 }
306 }
247 };
307 };
248 // submit form on Meta-Enter
308 // submit form on Meta-Enter
249 if (OSType === "mac") {
309 if (OSType === "mac") {
250 extraKeys["Cmd-Enter"] = submitForm;
310 extraKeys["Cmd-Enter"] = submitForm;
251 }
311 }
252 else {
312 else {
253 extraKeys["Ctrl-Enter"] = submitForm;
313 extraKeys["Ctrl-Enter"] = submitForm;
254 }
314 }
255
315
256 if (triggerActions) {
316 if (triggerActions) {
257 extraKeys["Ctrl-Space"] = completeActions;
317 extraKeys["Ctrl-Space"] = completeActions;
258 }
318 }
259
319
260 var cm = CodeMirror.fromTextArea($(textAreaId).get(0), {
320 var cm = CodeMirror.fromTextArea($(textAreaId).get(0), {
261 lineNumbers: false,
321 lineNumbers: false,
262 indentUnit: 4,
322 indentUnit: 4,
263 viewportMargin: 30,
323 viewportMargin: 30,
264 // this is a trick to trigger some logic behind codemirror placeholder
324 // this is a trick to trigger some logic behind codemirror placeholder
265 // it influences styling and behaviour.
325 // it influences styling and behaviour.
266 placeholder: " ",
326 placeholder: " ",
267 extraKeys: extraKeys,
327 extraKeys: extraKeys,
268 lineWrapping: true
328 lineWrapping: true
269 });
329 });
270
330
271 cm.setSize(null, initialHeight);
331 cm.setSize(null, initialHeight);
272 cm.setOption("mode", DEFAULT_RENDERER);
332 cm.setOption("mode", DEFAULT_RENDERER);
273 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
333 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
274 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
334 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
275 // start listening on changes to make auto-expanded editor
335 // start listening on changes to make auto-expanded editor
276 cm.on("change", function(self) {
336 cm.on("change", function(self) {
277 var height = initialHeight;
337 var height = initialHeight;
278 var lines = self.lineCount();
338 var lines = self.lineCount();
279 if ( lines > 6 && lines < 20) {
339 if ( lines > 6 && lines < 20) {
280 height = "auto";
340 height = "auto";
281 }
341 }
282 else if (lines >= 20){
342 else if (lines >= 20){
283 zheight = 20*15;
343 zheight = 20*15;
284 }
344 }
285 self.setSize(null, height);
345 self.setSize(null, height);
286 });
346 });
287
347
288 var mentionHint = function(editor, callback, options) {
289 var cur = editor.getCursor();
290 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
291
292 // match on @ +1char
293 var tokenMatch = new RegExp(
294 '(^@| @)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*)$').exec(curLine);
295
296 var tokenStr = '';
297 if (tokenMatch !== null && tokenMatch.length > 0){
298 tokenStr = tokenMatch[0].strip();
299 } else {
300 // skip if we didn't match our token
301 return;
302 }
303
304 var context = {
305 start: (cur.ch - tokenStr.length) + 1,
306 end: cur.ch,
307 string: tokenStr.slice(1),
308 type: null
309 };
310
311 // case when we put the @sign in fron of a string,
312 // eg <@ we put it here>sometext then we need to prepend to text
313 if (context.end > cur.ch) {
314 context.start = context.start + 1; // we add to the @ sign
315 context.end = cur.ch; // don't eat front part just append
316 context.string = context.string.slice(1, cur.ch - context.start);
317 }
318
319 cmLog.debug('Mention context', context);
320
321 var triggerHints = function(userHints){
322 return callback({
323 list: filterUsers(userHints, context),
324 from: CodeMirror.Pos(cur.line, context.start),
325 to: CodeMirror.Pos(cur.line, context.end)
326 });
327 };
328
329 var queryBasedHintsCache = undefined;
330 // if we have something in the cache, try to fetch the query based cache
331 if (userHintsCache !== {}){
332 queryBasedHintsCache = userHintsCache[context.string];
333 }
334
335 if (queryBasedHintsCache !== undefined) {
336 cmLog.debug('Users loaded from cache');
337 triggerHints(queryBasedHintsCache);
338 } else {
339 // this takes care for async loading, and then displaying results
340 // and also propagates the userHintsCache
341 window.clearTimeout(loadUserHintTimer);
342 loadUserHintTimer = setTimeout(function() {
343 loadUserHints(context.string, triggerHints);
344 }, 300);
345 }
346 };
347
348 var actionHint = function(editor, options) {
348 var actionHint = function(editor, options) {
349 var cur = editor.getCursor();
349 var cur = editor.getCursor();
350 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
350 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
351
351
352 var tokenMatch = new RegExp('[a-zA-Z]{1}[a-zA-Z]*$').exec(curLine);
352 var tokenMatch = new RegExp('[a-zA-Z]{1}[a-zA-Z]*$').exec(curLine);
353
353
354 var tokenStr = '';
354 var tokenStr = '';
355 if (tokenMatch !== null && tokenMatch.length > 0){
355 if (tokenMatch !== null && tokenMatch.length > 0){
356 tokenStr = tokenMatch[0].strip();
356 tokenStr = tokenMatch[0].strip();
357 }
357 }
358
358
359 var context = {
359 var context = {
360 start: cur.ch - tokenStr.length,
360 start: cur.ch - tokenStr.length,
361 end: cur.ch,
361 end: cur.ch,
362 string: tokenStr,
362 string: tokenStr,
363 type: null
363 type: null
364 };
364 };
365
365
366 var actions = [
366 var actions = [
367 {
367 {
368 text: "approve",
368 text: "approve",
369 displayText: _gettext('Set status to Approved'),
369 displayText: _gettext('Set status to Approved'),
370 hint: function(CodeMirror, data, completion) {
370 hint: function(CodeMirror, data, completion) {
371 CodeMirror.replaceRange("", completion.from || data.from,
371 CodeMirror.replaceRange("", completion.from || data.from,
372 completion.to || data.to, "complete");
372 completion.to || data.to, "complete");
373 $('#change_status').select2("val", 'approved').trigger('change');
373 $('#change_status').select2("val", 'approved').trigger('change');
374 },
374 },
375 render: function(elt, data, completion) {
375 render: function(elt, data, completion) {
376 var el = document.createElement('div');
376 var el = document.createElement('div');
377 el.className = "flag_status flag_status_comment_box approved pull-left";
377 el.className = "flag_status flag_status_comment_box approved pull-left";
378 elt.appendChild(el);
378 elt.appendChild(el);
379
379
380 el = document.createElement('span');
380 el = document.createElement('span');
381 el.innerHTML = completion.displayText;
381 el.innerHTML = completion.displayText;
382 elt.appendChild(el);
382 elt.appendChild(el);
383 }
383 }
384 },
384 },
385 {
385 {
386 text: "reject",
386 text: "reject",
387 displayText: _gettext('Set status to Rejected'),
387 displayText: _gettext('Set status to Rejected'),
388 hint: function(CodeMirror, data, completion) {
388 hint: function(CodeMirror, data, completion) {
389 CodeMirror.replaceRange("", completion.from || data.from,
389 CodeMirror.replaceRange("", completion.from || data.from,
390 completion.to || data.to, "complete");
390 completion.to || data.to, "complete");
391 $('#change_status').select2("val", 'rejected').trigger('change');
391 $('#change_status').select2("val", 'rejected').trigger('change');
392 },
392 },
393 render: function(elt, data, completion) {
393 render: function(elt, data, completion) {
394 var el = document.createElement('div');
394 var el = document.createElement('div');
395 el.className = "flag_status flag_status_comment_box rejected pull-left";
395 el.className = "flag_status flag_status_comment_box rejected pull-left";
396 elt.appendChild(el);
396 elt.appendChild(el);
397
397
398 el = document.createElement('span');
398 el = document.createElement('span');
399 el.innerHTML = completion.displayText;
399 el.innerHTML = completion.displayText;
400 elt.appendChild(el);
400 elt.appendChild(el);
401 }
401 }
402 }
402 }
403 ];
403 ];
404
404
405 return {
405 return {
406 list: filterActions(actions, context),
406 list: filterActions(actions, context),
407 from: CodeMirror.Pos(cur.line, context.start),
407 from: CodeMirror.Pos(cur.line, context.start),
408 to: CodeMirror.Pos(cur.line, context.end)
408 to: CodeMirror.Pos(cur.line, context.end)
409 };
409 };
410 };
410 };
411 CodeMirror.registerHelper("hint", "mentions", mentionHint);
411 CodeMirror.registerHelper("hint", "mentions", CodeMirrorMentionHint);
412 CodeMirror.registerHelper("hint", "actions", actionHint);
412 CodeMirror.registerHelper("hint", "actions", actionHint);
413 return cm;
413 return cm;
414 };
414 };
415
415
416 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
416 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
417 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
417 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
418 codeMirrorInstance.setOption("mode", mode);
418 codeMirrorInstance.setOption("mode", mode);
419 };
419 };
420
420
421 var setCodeMirrorLineWrap = function(codeMirrorInstance, line_wrap) {
421 var setCodeMirrorLineWrap = function(codeMirrorInstance, line_wrap) {
422 codeMirrorInstance.setOption("lineWrapping", line_wrap);
422 codeMirrorInstance.setOption("lineWrapping", line_wrap);
423 };
423 };
424
424
425 var setCodeMirrorModeFromSelect = function(
425 var setCodeMirrorModeFromSelect = function(
426 targetSelect, targetFileInput, codeMirrorInstance, callback){
426 targetSelect, targetFileInput, codeMirrorInstance, callback){
427
427
428 $(targetSelect).on('change', function(e) {
428 $(targetSelect).on('change', function(e) {
429 cmLog.debug('codemirror select2 mode change event !');
429 cmLog.debug('codemirror select2 mode change event !');
430 var selected = e.currentTarget;
430 var selected = e.currentTarget;
431 var node = selected.options[selected.selectedIndex];
431 var node = selected.options[selected.selectedIndex];
432 var mimetype = node.value;
432 var mimetype = node.value;
433 cmLog.debug('picked mimetype', mimetype);
433 cmLog.debug('picked mimetype', mimetype);
434 var new_mode = $(node).attr('mode');
434 var new_mode = $(node).attr('mode');
435 setCodeMirrorMode(codeMirrorInstance, new_mode);
435 setCodeMirrorMode(codeMirrorInstance, new_mode);
436 cmLog.debug('set new mode', new_mode);
436 cmLog.debug('set new mode', new_mode);
437
437
438 //propose filename from picked mode
438 //propose filename from picked mode
439 cmLog.debug('setting mimetype', mimetype);
439 cmLog.debug('setting mimetype', mimetype);
440 var proposed_ext = getExtFromMimeType(mimetype);
440 var proposed_ext = getExtFromMimeType(mimetype);
441 cmLog.debug('file input', $(targetFileInput).val());
441 cmLog.debug('file input', $(targetFileInput).val());
442 var file_data = getFilenameAndExt($(targetFileInput).val());
442 var file_data = getFilenameAndExt($(targetFileInput).val());
443 var filename = file_data.filename || 'filename1';
443 var filename = file_data.filename || 'filename1';
444 $(targetFileInput).val(filename + proposed_ext);
444 $(targetFileInput).val(filename + proposed_ext);
445 cmLog.debug('proposed file', filename + proposed_ext);
445 cmLog.debug('proposed file', filename + proposed_ext);
446
446
447
447
448 if (typeof(callback) === 'function') {
448 if (typeof(callback) === 'function') {
449 try {
449 try {
450 cmLog.debug('running callback', callback);
450 cmLog.debug('running callback', callback);
451 callback(filename, mimetype, new_mode);
451 callback(filename, mimetype, new_mode);
452 } catch (err) {
452 } catch (err) {
453 console.log('failed to run callback', callback, err);
453 console.log('failed to run callback', callback, err);
454 }
454 }
455 }
455 }
456 cmLog.debug('finish iteration...');
456 cmLog.debug('finish iteration...');
457 });
457 });
458 };
458 };
459
459
460 var setCodeMirrorModeFromInput = function(
460 var setCodeMirrorModeFromInput = function(
461 targetSelect, targetFileInput, codeMirrorInstance, callback) {
461 targetSelect, targetFileInput, codeMirrorInstance, callback) {
462
462
463 // on type the new filename set mode
463 // on type the new filename set mode
464 $(targetFileInput).on('keyup', function(e) {
464 $(targetFileInput).on('keyup', function(e) {
465 var file_data = getFilenameAndExt(this.value);
465 var file_data = getFilenameAndExt(this.value);
466 if (file_data.ext === null) {
466 if (file_data.ext === null) {
467 return;
467 return;
468 }
468 }
469
469
470 var mimetypes = getMimeTypeFromExt(file_data.ext, true);
470 var mimetypes = getMimeTypeFromExt(file_data.ext, true);
471 cmLog.debug('mimetype from file', file_data, mimetypes);
471 cmLog.debug('mimetype from file', file_data, mimetypes);
472 var detected_mode;
472 var detected_mode;
473 var detected_option;
473 var detected_option;
474 for (var i in mimetypes) {
474 for (var i in mimetypes) {
475 var mt = mimetypes[i];
475 var mt = mimetypes[i];
476 if (!detected_mode) {
476 if (!detected_mode) {
477 detected_mode = detectCodeMirrorMode(this.value, mt);
477 detected_mode = detectCodeMirrorMode(this.value, mt);
478 }
478 }
479
479
480 if (!detected_option) {
480 if (!detected_option) {
481 cmLog.debug('#mimetype option[value="{0}"]'.format(mt));
481 cmLog.debug('#mimetype option[value="{0}"]'.format(mt));
482 if ($(targetSelect).find('option[value="{0}"]'.format(mt)).length) {
482 if ($(targetSelect).find('option[value="{0}"]'.format(mt)).length) {
483 detected_option = mt;
483 detected_option = mt;
484 }
484 }
485 }
485 }
486 }
486 }
487
487
488 cmLog.debug('detected mode', detected_mode);
488 cmLog.debug('detected mode', detected_mode);
489 cmLog.debug('detected option', detected_option);
489 cmLog.debug('detected option', detected_option);
490 if (detected_mode && detected_option){
490 if (detected_mode && detected_option){
491
491
492 $(targetSelect).select2("val", detected_option);
492 $(targetSelect).select2("val", detected_option);
493 setCodeMirrorMode(codeMirrorInstance, detected_mode);
493 setCodeMirrorMode(codeMirrorInstance, detected_mode);
494
494
495 if(typeof(callback) === 'function'){
495 if(typeof(callback) === 'function'){
496 try{
496 try{
497 cmLog.debug('running callback', callback);
497 cmLog.debug('running callback', callback);
498 var filename = file_data.filename + "." + file_data.ext;
498 var filename = file_data.filename + "." + file_data.ext;
499 callback(filename, detected_option, detected_mode);
499 callback(filename, detected_option, detected_mode);
500 }catch (err){
500 }catch (err){
501 console.log('failed to run callback', callback, err);
501 console.log('failed to run callback', callback, err);
502 }
502 }
503 }
503 }
504 }
504 }
505
505
506 });
506 });
507 };
507 };
508
508
509 var fillCodeMirrorOptions = function(targetSelect) {
509 var fillCodeMirrorOptions = function(targetSelect) {
510 //inject new modes, based on codeMirrors modeInfo object
510 //inject new modes, based on codeMirrors modeInfo object
511 var modes_select = $(targetSelect);
511 var modes_select = $(targetSelect);
512 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
512 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
513 var m = CodeMirror.modeInfo[i];
513 var m = CodeMirror.modeInfo[i];
514 var opt = new Option(m.name, m.mime);
514 var opt = new Option(m.name, m.mime);
515 $(opt).attr('mode', m.mode);
515 $(opt).attr('mode', m.mode);
516 modes_select.append(opt);
516 modes_select.append(opt);
517 }
517 }
518 };
518 };
519
519
520 var CodeMirrorPreviewEnable = function(edit_mode) {
520 var CodeMirrorPreviewEnable = function(edit_mode) {
521 // in case it a preview enabled mode enable the button
521 // in case it a preview enabled mode enable the button
522 if (['markdown', 'rst', 'gfm'].indexOf(edit_mode) !== -1) {
522 if (['markdown', 'rst', 'gfm'].indexOf(edit_mode) !== -1) {
523 $('#render_preview').removeClass('hidden');
523 $('#render_preview').removeClass('hidden');
524 }
524 }
525 else {
525 else {
526 if (!$('#render_preview').hasClass('hidden')) {
526 if (!$('#render_preview').hasClass('hidden')) {
527 $('#render_preview').addClass('hidden');
527 $('#render_preview').addClass('hidden');
528 }
528 }
529 }
529 }
530 };
530 };
General Comments 0
You need to be logged in to leave comments. Login now