##// END OF EJS Templates
comments: fixed codemirror styling problems.
marcink -
r4131:726badcf default
parent child Browse files
Show More
@@ -1,438 +1,439 b''
1 1 /* BASICS */
2 2
3 3 .CodeMirror {
4 4 /* Set height, width, borders, and global font properties here */
5 5 font-family: monospace;
6 6 height: 300px;
7 7 color: black;
8 8 direction: ltr;
9 9 border-radius: @border-radius;
10 10 border: @border-thickness solid @grey6;
11 11 margin: 0 0 @padding;
12 12 }
13 13
14 14 /* PADDING */
15 15
16 16 .CodeMirror-lines {
17 17 padding: 4px 0; /* Vertical padding around content */
18 18 }
19 19 .CodeMirror pre.CodeMirror-line,
20 20 .CodeMirror pre.CodeMirror-line-like {
21 21 padding: 0 4px; /* Horizontal padding of content */
22 22 }
23 23
24 24 .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
25 25 background-color: white; /* The little square between H and V scrollbars */
26 26 }
27 27
28 28 /* GUTTER */
29 29
30 30 .CodeMirror-gutters {
31 31 border-right: 1px solid #ddd;
32 32 background-color: white;
33 33 white-space: nowrap;
34 34 }
35 35 .CodeMirror-linenumbers {}
36 36 .CodeMirror-linenumber {
37 37 padding: 0 3px 0 5px;
38 38 min-width: 20px;
39 39 text-align: right;
40 40 color: @grey4;
41 41 white-space: nowrap;
42 42 }
43 43
44 44 .CodeMirror-guttermarker { color: black; }
45 45 .CodeMirror-guttermarker-subtle { color: #999; }
46 46
47 47 /* CURSOR */
48 48
49 49 .CodeMirror-cursor {
50 50 border-left: 1px solid black;
51 51 border-right: none;
52 52 width: 0;
53 53 }
54 54 /* Shown when moving in bi-directional text */
55 55 .CodeMirror div.CodeMirror-secondarycursor {
56 56 border-left: 1px solid silver;
57 57 }
58 58 .cm-fat-cursor .CodeMirror-cursor {
59 59 width: auto;
60 60 border: 0 !important;
61 61 background: @grey6;
62 62 }
63 63 .cm-fat-cursor div.CodeMirror-cursors {
64 64 z-index: 1;
65 65 }
66 66 .cm-fat-cursor-mark {
67 67 background-color: rgba(20, 255, 20, 0.5);
68 68 -webkit-animation: blink 1.06s steps(1) infinite;
69 69 -moz-animation: blink 1.06s steps(1) infinite;
70 70 animation: blink 1.06s steps(1) infinite;
71 71 }
72 72 .cm-animate-fat-cursor {
73 73 width: auto;
74 74 border: 0;
75 75 -webkit-animation: blink 1.06s steps(1) infinite;
76 76 -moz-animation: blink 1.06s steps(1) infinite;
77 77 animation: blink 1.06s steps(1) infinite;
78 78 background-color: #7e7;
79 79 }
80 80 @-moz-keyframes blink {
81 81 0% { background: #7e7; }
82 82 50% { background: none; }
83 83 100% { background: #7e7; }
84 84 }
85 85 @-webkit-keyframes blink {
86 86 0% { background: #7e7; }
87 87 50% { background: none; }
88 88 100% { background: #7e7; }
89 89 }
90 90 @keyframes blink {
91 91 0% { background: #7e7; }
92 92 50% { background: none; }
93 93 100% { background: #7e7; }
94 94 }
95 95
96 96 /* Can style cursor different in overwrite (non-insert) mode */
97 97 .CodeMirror-overwrite .CodeMirror-cursor {}
98 98
99 99 .cm-tab { display: inline-block; text-decoration: inherit; }
100 100
101 101 .CodeMirror-rulers {
102 102 position: absolute;
103 103 left: 0; right: 0; top: -50px; bottom: 0;
104 104 overflow: hidden;
105 105 }
106 106 .CodeMirror-ruler {
107 107 border-left: 1px solid #ccc;
108 108 top: 0; bottom: 0;
109 109 position: absolute;
110 110 }
111 111
112 112 /* DEFAULT THEME */
113 113
114 114 .cm-s-default .cm-header {color: blue;}
115 115 .cm-s-default .cm-quote {color: #090;}
116 116 .cm-negative {color: #d44;}
117 117 .cm-positive {color: #292;}
118 118 .cm-header, .cm-strong {font-weight: bold;}
119 119 .cm-em {font-style: italic;}
120 120 .cm-link {text-decoration: underline;}
121 121 .cm-strikethrough {text-decoration: line-through;}
122 122
123 123 .cm-s-default .cm-keyword {color: #708;}
124 124 .cm-s-default .cm-atom {color: #219;}
125 125 .cm-s-default .cm-number {color: #164;}
126 126 .cm-s-default .cm-def {color: #00f;}
127 127 .cm-s-default .cm-variable,
128 128 .cm-s-default .cm-punctuation,
129 129 .cm-s-default .cm-property,
130 130 .cm-s-default .cm-operator {}
131 131 .cm-s-default .cm-variable-2 {color: #05a;}
132 132 .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
133 133 .cm-s-default .cm-comment {color: #a50;}
134 134 .cm-s-default .cm-string {color: #a11;}
135 135 .cm-s-default .cm-string-2 {color: #f50;}
136 136 .cm-s-default .cm-meta {color: #555;}
137 137 .cm-s-default .cm-qualifier {color: #555;}
138 138 .cm-s-default .cm-builtin {color: #30a;}
139 139 .cm-s-default .cm-bracket {color: #997;}
140 140 .cm-s-default .cm-tag {color: #170;}
141 141 .cm-s-default .cm-attribute {color: #00c;}
142 142 .cm-s-default .cm-hr {color: #999;}
143 143 .cm-s-default .cm-link {color: #00c;}
144 144
145 145 .cm-s-default .cm-error {color: #f00;}
146 146 .cm-invalidchar {color: #f00;}
147 147
148 148 .CodeMirror-composing { border-bottom: 2px solid; }
149 149
150 150 /* Default styles for common addons */
151 151
152 152 div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
153 153 div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
154 154 .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
155 155 .CodeMirror-activeline-background {background: #e8f2ff;}
156 156
157 157 /* STOP */
158 158
159 159 /* The rest of this file contains styles related to the mechanics of
160 160 the editor. You probably shouldn't touch them. */
161 161
162 162 .CodeMirror {
163 163 position: relative;
164 164 overflow: hidden;
165 165 background: white;
166 166 }
167 167
168 168 .CodeMirror-scroll {
169 169 overflow: scroll !important; /* Things will break if this is overridden */
170 170 /* 30px is the magic margin used to hide the element's real scrollbars */
171 171 /* See overflow: hidden in .CodeMirror */
172 172 margin-bottom: -30px; margin-right: -30px;
173 173 padding-bottom: 30px;
174 174 height: 100%;
175 175 outline: none; /* Prevent dragging from highlighting the element */
176 176 position: relative;
177 177 }
178 178 .CodeMirror-sizer {
179 179 position: relative;
180 180 border-right: 30px solid transparent;
181 181 }
182 182
183 183 /* The fake, visible scrollbars. Used to force redraw during scrolling
184 184 before actual scrolling happens, thus preventing shaking and
185 185 flickering artifacts. */
186 186 .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
187 187 position: absolute;
188 188 z-index: 6;
189 189 display: none;
190 190 }
191 191 .CodeMirror-vscrollbar {
192 192 right: 0; top: 0;
193 193 overflow-x: hidden;
194 194 overflow-y: scroll;
195 195 }
196 196 .CodeMirror-hscrollbar {
197 197 bottom: 0; left: 0;
198 198 overflow-y: hidden;
199 199 overflow-x: scroll;
200 200 }
201 201 .CodeMirror-scrollbar-filler {
202 202 right: 0; bottom: 0;
203 203 }
204 204 .CodeMirror-gutter-filler {
205 205 left: 0; bottom: 0;
206 206 }
207 207
208 208 .CodeMirror-gutters {
209 209 position: absolute; left: 0; top: 0;
210 210 min-height: 100%;
211 211 z-index: 3;
212 212 }
213 213 .CodeMirror-gutter {
214 214 white-space: normal;
215 215 height: 100%;
216 216 display: inline-block;
217 217 vertical-align: top;
218 218 margin-bottom: -30px;
219 219 }
220 220 .CodeMirror-gutter-wrapper {
221 221 position: absolute;
222 222 z-index: 4;
223 223 background: none !important;
224 224 border: none !important;
225 225 height: 100%;
226 226 }
227 227 .CodeMirror-gutter-background {
228 228 position: absolute;
229 229 top: 0; bottom: 0;
230 230 z-index: 4;
231 231 }
232 232 .CodeMirror-gutter-elt {
233 233 position: absolute;
234 234 cursor: default;
235 235 z-index: 4;
236 236 }
237 237 .CodeMirror-gutter-wrapper {
238 238 -webkit-user-select: none;
239 239 -moz-user-select: none;
240 240 user-select: none;
241 241 }
242 242
243 243 .CodeMirror-lines {
244 244 cursor: text;
245 245 min-height: 1px; /* prevents collapsing before first draw */
246 246 }
247 247 .CodeMirror pre.CodeMirror-line,
248 248 .CodeMirror pre.CodeMirror-line-like {
249 249 /* Reset some styles that the rest of the page might have set */
250 250 -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
251 251 border-width: 0;
252 252 background: transparent;
253 253 font-family: inherit;
254 254 font-size: inherit;
255 255 margin: 0;
256 256 white-space: pre;
257 257 word-wrap: normal;
258 258 line-height: inherit;
259 259 color: inherit;
260 260 z-index: 2;
261 261 position: relative;
262 262 overflow: visible;
263 263 -webkit-tap-highlight-color: transparent;
264 264 -webkit-font-variant-ligatures: contextual;
265 265 font-variant-ligatures: contextual;
266 266 }
267 267 .CodeMirror-wrap pre.CodeMirror-line,
268 268 .CodeMirror-wrap pre.CodeMirror-line-like {
269 269 word-wrap: break-word;
270 270 white-space: pre-wrap;
271 271 word-break: normal;
272 272 }
273 273
274 274 .CodeMirror-linebackground {
275 275 position: absolute;
276 276 left: 0; right: 0; top: 0; bottom: 0;
277 277 z-index: 0;
278 278 }
279 279
280 280 .CodeMirror-linewidget {
281 281 position: relative;
282 282 z-index: 2;
283 283 padding: 0.1px; /* Force widget margins to stay inside of the container */
284 284 overflow: auto;
285 285 }
286 286
287 287 .CodeMirror-widget {}
288 288
289 289 .CodeMirror-rtl pre { direction: rtl; }
290 290
291 291 .CodeMirror-code {
292 292 outline: none;
293 293 }
294 294
295 295 /* Force content-box sizing for the elements where we expect it */
296 296 .CodeMirror-scroll,
297 297 .CodeMirror-sizer,
298 298 .CodeMirror-gutter,
299 299 .CodeMirror-gutters,
300 300 .CodeMirror-linenumber {
301 -moz-box-sizing: content-box;
302 box-sizing: content-box;
301 /* RhodeCode added !important, to fix diffs rule */
302 -moz-box-sizing: content-box !important;
303 box-sizing: content-box !important;
303 304 }
304 305
305 306 .CodeMirror-measure {
306 307 position: absolute;
307 308 width: 100%;
308 309 height: 0;
309 310 overflow: hidden;
310 311 visibility: hidden;
311 312 }
312 313
313 314 .CodeMirror-cursor {
314 315 position: absolute;
315 316 pointer-events: none;
316 317 border-right: none;
317 318 width: 0;
318 319 }
319 320 .CodeMirror-measure pre { position: static; }
320 321
321 322 div.CodeMirror-cursors {
322 323 visibility: hidden;
323 324 position: relative;
324 325 z-index: 3;
325 326 }
326 327 div.CodeMirror-dragcursors {
327 328 visibility: visible;
328 329 }
329 330
330 331 .CodeMirror-focused div.CodeMirror-cursors {
331 332 visibility: visible;
332 333 }
333 334
334 335 .CodeMirror-selected { background: #d9d9d9; }
335 336 .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
336 337 .CodeMirror-crosshair { cursor: crosshair; }
337 338 .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
338 339 .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
339 340
340 341 .cm-searching {
341 342 background-color: #ffa;
342 343 background-color: rgba(255, 255, 0, .4);
343 344 }
344 345
345 346 /* Used to force a border model for a node */
346 347 .cm-force-border { padding-right: .1px; }
347 348
348 349 @media print {
349 350 /* Hide the cursor when printing */
350 351 .CodeMirror div.CodeMirror-cursors {
351 352 visibility: hidden;
352 353 }
353 354 }
354 355
355 356 /* See issue #2901 */
356 357 .cm-tab-wrap-hack:after { content: ''; }
357 358
358 359 /* Help users use markselection to safely style text background */
359 360 span.CodeMirror-selectedtext { background: none; }
360 361
361 362 /* codemirror autocomplete widget */
362 363 .CodeMirror-hints {
363 364 position: absolute;
364 365 z-index: 10;
365 366 overflow: hidden;
366 367 list-style: none;
367 368
368 369 margin: 0;
369 370 padding: 0;
370 371
371 372 border-radius: @border-radius;
372 373 border: @border-thickness solid @rcblue;
373 374
374 375 color: @rcblue;
375 376 background-color: white;
376 377 font-size: 95%;
377 378
378 379 max-height: 20em;
379 380 overflow-y: auto;
380 381 }
381 382
382 383 .CodeMirror-hint {
383 384 margin: 0;
384 385 padding: 4px 8px;
385 386 max-width: 40em;
386 387 white-space: pre;
387 388 color: @rcblue;
388 389 cursor: pointer;
389 390 }
390 391
391 392 .CodeMirror-hint-active {
392 393 background: @rclightblue;
393 394 color: @rcblue;
394 395 }
395 396
396 397 .CodeMirror-hint-entry {
397 398 width: 38em;
398 399 color: @rcblue;
399 400 }
400 401
401 402 .CodeMirror-hint-entry .gravatar {
402 403 height: @gravatar-size;
403 404 width: @gravatar-size;
404 405 margin-right: 4px;
405 406 }
406 407
407 408 .CodeMirror-empty {
408 409 border: @border-thickness solid @grey5;
409 410 }
410 411
411 412 .CodeMirror-focused {
412 413 border: @border-thickness solid @grey5;
413 414 }
414 415
415 416 .CodeMirror-empty.CodeMirror-focused {
416 417 border: @border-thickness solid @grey5;
417 418 }
418 419
419 420 .CodeMirror pre.CodeMirror-placeholder {
420 421 color: @grey4;
421 422 }
422 423
423 424 /** RhodeCode Customizations **/
424 425
425 426 .CodeMirror.cm-s-rc-input {
426 427 border: @border-thickness solid @grey4;
427 428 }
428 429
429 430 .CodeMirror-code pre {
430 431 border-right: 30px solid transparent;
431 432 width: -webkit-fit-content;
432 433 width: -moz-fit-content;
433 434 width: fit-content;
434 435 }
435 436 .CodeMirror-wrap .CodeMirror-code pre {
436 437 border-right: none;
437 438 width: auto;
438 439 }
@@ -1,844 +1,843 b''
1 1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 * Code Mirror
21 21 */
22 22 // global code-mirror logger;, to enable run
23 23 // Logger.get('CodeMirror').setLevel(Logger.DEBUG)
24 24
25 25 cmLog = Logger.get('CodeMirror');
26 26 cmLog.setLevel(Logger.OFF);
27 27
28 28
29 29 //global cache for inline forms
30 30 var userHintsCache = {};
31 31
32 32 // global timer, used to cancel async loading
33 33 var CodeMirrorLoadUserHintTimer;
34 34
35 35 var escapeRegExChars = function(value) {
36 36 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
37 37 };
38 38
39 39 /**
40 40 * Load hints from external source returns an array of objects in a format
41 41 * that hinting lib requires
42 42 * @returns {Array}
43 43 */
44 44 var CodeMirrorLoadUserHints = function(query, triggerHints) {
45 45 cmLog.debug('Loading mentions users via AJAX');
46 46 var _users = [];
47 47 $.ajax({
48 48 type: 'GET',
49 49 data: {query: query},
50 50 url: pyroutes.url('user_autocomplete_data'),
51 51 headers: {'X-PARTIAL-XHR': true},
52 52 async: true
53 53 })
54 54 .done(function(data) {
55 55 var tmpl = '<img class="gravatar" src="{0}"/>{1}';
56 56 $.each(data.suggestions, function(i) {
57 57 var userObj = data.suggestions[i];
58 58
59 59 if (userObj.username !== "default") {
60 60 _users.push({
61 61 text: userObj.username + " ",
62 62 org_text: userObj.username,
63 63 displayText: userObj.value_display, // search that field
64 64 // internal caches
65 65 _icon_link: userObj.icon_link,
66 66 _text: userObj.value_display,
67 67
68 68 render: function(elt, data, completion) {
69 69 var el = document.createElement('div');
70 70 el.className = "CodeMirror-hint-entry";
71 71 el.innerHTML = tmpl.format(
72 72 completion._icon_link, completion._text);
73 73 elt.appendChild(el);
74 74 }
75 75 });
76 76 }
77 77 });
78 78 cmLog.debug('Mention users loaded');
79 79 // set to global cache
80 80 userHintsCache[query] = _users;
81 81 triggerHints(userHintsCache[query]);
82 82 })
83 83 .fail(function(data, textStatus, xhr) {
84 84 alert("error processing request. \n" +
85 85 "Error code {0} ({1}).".format(data.status, data.statusText));
86 86 });
87 87 };
88 88
89 89 /**
90 90 * filters the results based on the current context
91 91 * @param users
92 92 * @param context
93 93 * @returns {Array}
94 94 */
95 95 var CodeMirrorFilterUsers = function(users, context) {
96 96 var MAX_LIMIT = 10;
97 97 var filtered_users = [];
98 98 var curWord = context.string;
99 99
100 100 cmLog.debug('Filtering users based on query:', curWord);
101 101 $.each(users, function(i) {
102 102 var match = users[i];
103 103 var searchText = match.displayText;
104 104
105 105 if (!curWord ||
106 106 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
107 107 // reset state
108 108 match._text = match.displayText;
109 109 if (curWord) {
110 110 // do highlighting
111 111 var pattern = '(' + escapeRegExChars(curWord) + ')';
112 112 match._text = searchText.replace(
113 113 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
114 114 }
115 115
116 116 filtered_users.push(match);
117 117 }
118 118 // to not return to many results, use limit of filtered results
119 119 if (filtered_users.length > MAX_LIMIT) {
120 120 return false;
121 121 }
122 122 });
123 123
124 124 return filtered_users;
125 125 };
126 126
127 127 var CodeMirrorMentionHint = function(editor, callback, options) {
128 128 var cur = editor.getCursor();
129 129 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
130 130
131 131 // match on @ +1char
132 132 var tokenMatch = new RegExp(
133 133 '(^@| @)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*)$').exec(curLine);
134 134
135 135 var tokenStr = '';
136 136 if (tokenMatch !== null && tokenMatch.length > 0){
137 137 tokenStr = tokenMatch[0].strip();
138 138 } else {
139 139 // skip if we didn't match our token
140 140 return;
141 141 }
142 142
143 143 var context = {
144 144 start: (cur.ch - tokenStr.length) + 1,
145 145 end: cur.ch,
146 146 string: tokenStr.slice(1),
147 147 type: null
148 148 };
149 149
150 150 // case when we put the @sign in fron of a string,
151 151 // eg <@ we put it here>sometext then we need to prepend to text
152 152 if (context.end > cur.ch) {
153 153 context.start = context.start + 1; // we add to the @ sign
154 154 context.end = cur.ch; // don't eat front part just append
155 155 context.string = context.string.slice(1, cur.ch - context.start);
156 156 }
157 157
158 158 cmLog.debug('Mention context', context);
159 159
160 160 var triggerHints = function(userHints){
161 161 return callback({
162 162 list: CodeMirrorFilterUsers(userHints, context),
163 163 from: CodeMirror.Pos(cur.line, context.start),
164 164 to: CodeMirror.Pos(cur.line, context.end)
165 165 });
166 166 };
167 167
168 168 var queryBasedHintsCache = undefined;
169 169 // if we have something in the cache, try to fetch the query based cache
170 170 if (userHintsCache !== {}){
171 171 queryBasedHintsCache = userHintsCache[context.string];
172 172 }
173 173
174 174 if (queryBasedHintsCache !== undefined) {
175 175 cmLog.debug('Users loaded from cache');
176 176 triggerHints(queryBasedHintsCache);
177 177 } else {
178 178 // this takes care for async loading, and then displaying results
179 179 // and also propagates the userHintsCache
180 180 window.clearTimeout(CodeMirrorLoadUserHintTimer);
181 181 CodeMirrorLoadUserHintTimer = setTimeout(function() {
182 182 CodeMirrorLoadUserHints(context.string, triggerHints);
183 183 }, 300);
184 184 }
185 185 };
186 186
187 187 var CodeMirrorCompleteAfter = function(cm, pred) {
188 188 var options = {
189 189 completeSingle: false,
190 190 async: true,
191 191 closeOnUnfocus: true
192 192 };
193 193 var cur = cm.getCursor();
194 194 setTimeout(function() {
195 195 if (!cm.state.completionActive) {
196 196 cmLog.debug('Trigger mentions hinting');
197 197 CodeMirror.showHint(cm, CodeMirror.hint.mentions, options);
198 198 }
199 199 }, 100);
200 200
201 201 // tell CodeMirror we didn't handle the key
202 202 // trick to trigger on a char but still complete it
203 203 return CodeMirror.Pass;
204 204 };
205 205
206 206 var initCodeMirror = function(textAreadId, resetUrl, focus, options) {
207 207 if (textAreadId.substr(0,1) === "#"){
208 208 var ta = $(textAreadId).get(0);
209 209 }else {
210 210 var ta = $('#' + textAreadId).get(0);
211 211 }
212 212
213 213 if (focus === undefined) {
214 214 focus = true;
215 215 }
216 216
217 217 // default options
218 218 var codeMirrorOptions = {
219 219 mode: "null",
220 220 lineNumbers: true,
221 221 indentUnit: 4,
222 222 autofocus: focus
223 223 };
224 224
225 225 if (options !== undefined) {
226 226 // extend with custom options
227 227 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
228 228 }
229 229
230 230 var myCodeMirror = CodeMirror.fromTextArea(ta, codeMirrorOptions);
231 231
232 232 $('#reset').on('click', function(e) {
233 233 window.location = resetUrl;
234 234 });
235 235
236 236 return myCodeMirror;
237 237 };
238 238
239 239
240 240 var initMarkupCodeMirror = function(textAreadId, focus, options) {
241 241 var initialHeight = 100;
242 242
243 243 var ta = $(textAreadId).get(0);
244 244 if (focus === undefined) {
245 245 focus = true;
246 246 }
247 247
248 248 // default options
249 249 var codeMirrorOptions = {
250 250 lineNumbers: false,
251 251 indentUnit: 4,
252 252 viewportMargin: 30,
253 253 // this is a trick to trigger some logic behind codemirror placeholder
254 254 // it influences styling and behaviour.
255 255 placeholder: " ",
256 256 lineWrapping: true,
257 257 autofocus: focus
258 258 };
259 259
260 260 if (options !== undefined) {
261 261 // extend with custom options
262 262 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
263 263 }
264 264
265 265 var cm = CodeMirror.fromTextArea(ta, codeMirrorOptions);
266 266 cm.setSize(null, initialHeight);
267 267 cm.setOption("mode", DEFAULT_RENDERER);
268 268 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
269 269 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
270 270
271 271 // start listening on changes to make auto-expanded editor
272 cm.on("change", function(instance, changeObj) {
272 cm.on("change", function (instance, changeObj) {
273 273 var height = initialHeight;
274 274 var lines = instance.lineCount();
275 if ( lines > 6 && lines < 20) {
275 if (lines > 6 && lines < 20) {
276 276 height = "auto";
277 }
278 else if (lines >= 20){
279 zheight = 20*15;
277 } else if (lines >= 20) {
278 height = 20 * 15;
280 279 }
281 280 instance.setSize(null, height);
282 281
283 282 // detect if the change was trigger by auto desc, or user input
284 283 var changeOrigin = changeObj.origin;
285 284
286 285 if (changeOrigin === "setValue") {
287 286 cmLog.debug('Change triggered by setValue');
288 287 }
289 288 else {
290 289 cmLog.debug('user triggered change !');
291 290 // set special marker to indicate user has created an input.
292 291 instance._userDefinedValue = true;
293 292 }
294 293
295 294 });
296 295
297 296 return cm;
298 297 };
299 298
300 299
301 300 var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions){
302 301 var initialHeight = 100;
303 302
304 303 if (typeof userHintsCache === "undefined") {
305 304 userHintsCache = {};
306 305 cmLog.debug('Init empty cache for mentions');
307 306 }
308 307 if (!$(textAreaId).get(0)) {
309 308 cmLog.debug('Element for textarea not found', textAreaId);
310 309 return;
311 310 }
312 311 /**
313 312 * Filter action based on typed in text
314 313 * @param actions
315 314 * @param context
316 315 * @returns {Array}
317 316 */
318 317
319 318 var filterActions = function(actions, context){
320 319
321 320 var MAX_LIMIT = 10;
322 321 var filtered_actions = [];
323 322 var curWord = context.string;
324 323
325 324 cmLog.debug('Filtering actions based on query:', curWord);
326 325 $.each(actions, function(i) {
327 326 var match = actions[i];
328 327 var searchText = match.searchText;
329 328
330 329 if (!curWord ||
331 330 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
332 331 // reset state
333 332 match._text = match.displayText;
334 333 if (curWord) {
335 334 // do highlighting
336 335 var pattern = '(' + escapeRegExChars(curWord) + ')';
337 336 match._text = searchText.replace(
338 337 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
339 338 }
340 339
341 340 filtered_actions.push(match);
342 341 }
343 342 // to not return to many results, use limit of filtered results
344 343 if (filtered_actions.length > MAX_LIMIT) {
345 344 return false;
346 345 }
347 346 });
348 347
349 348 return filtered_actions;
350 349 };
351 350
352 351 var submitForm = function(cm, pred) {
353 352 $(cm.display.input.textarea.form).submit();
354 353 return CodeMirror.Pass;
355 354 };
356 355
357 356 var completeActions = function(actions){
358 357
359 358 var registeredActions = [];
360 359 var allActions = [
361 360 {
362 361 text: "approve",
363 362 searchText: "status approved",
364 363 displayText: _gettext('Set status to Approved'),
365 364 hint: function(CodeMirror, data, completion) {
366 365 CodeMirror.replaceRange("", completion.from || data.from,
367 366 completion.to || data.to, "complete");
368 367 $(CommentForm.statusChange).select2("val", 'approved').trigger('change');
369 368 },
370 369 render: function(elt, data, completion) {
371 370 var el = document.createElement('i');
372 371
373 372 el.className = "icon-circle review-status-approved";
374 373 elt.appendChild(el);
375 374
376 375 el = document.createElement('span');
377 376 el.innerHTML = completion.displayText;
378 377 elt.appendChild(el);
379 378 }
380 379 },
381 380 {
382 381 text: "reject",
383 382 searchText: "status rejected",
384 383 displayText: _gettext('Set status to Rejected'),
385 384 hint: function(CodeMirror, data, completion) {
386 385 CodeMirror.replaceRange("", completion.from || data.from,
387 386 completion.to || data.to, "complete");
388 387 $(CommentForm.statusChange).select2("val", 'rejected').trigger('change');
389 388 },
390 389 render: function(elt, data, completion) {
391 390 var el = document.createElement('i');
392 391 el.className = "icon-circle review-status-rejected";
393 392 elt.appendChild(el);
394 393
395 394 el = document.createElement('span');
396 395 el.innerHTML = completion.displayText;
397 396 elt.appendChild(el);
398 397 }
399 398 },
400 399 {
401 400 text: "as_todo",
402 401 searchText: "todo comment",
403 402 displayText: _gettext('TODO comment'),
404 403 hint: function(CodeMirror, data, completion) {
405 404 CodeMirror.replaceRange("", completion.from || data.from,
406 405 completion.to || data.to, "complete");
407 406
408 407 $(CommentForm.commentType).val('todo');
409 408 },
410 409 render: function(elt, data, completion) {
411 410 var el = document.createElement('div');
412 411 el.className = "pull-left";
413 412 elt.appendChild(el);
414 413
415 414 el = document.createElement('span');
416 415 el.innerHTML = completion.displayText;
417 416 elt.appendChild(el);
418 417 }
419 418 },
420 419 {
421 420 text: "as_note",
422 421 searchText: "note comment",
423 422 displayText: _gettext('Note Comment'),
424 423 hint: function(CodeMirror, data, completion) {
425 424 CodeMirror.replaceRange("", completion.from || data.from,
426 425 completion.to || data.to, "complete");
427 426
428 427 $(CommentForm.commentType).val('note');
429 428 },
430 429 render: function(elt, data, completion) {
431 430 var el = document.createElement('div');
432 431 el.className = "pull-left";
433 432 elt.appendChild(el);
434 433
435 434 el = document.createElement('span');
436 435 el.innerHTML = completion.displayText;
437 436 elt.appendChild(el);
438 437 }
439 438 }
440 439 ];
441 440
442 441 $.each(allActions, function(index, value){
443 442 var actionData = allActions[index];
444 443 if (actions.indexOf(actionData['text']) != -1) {
445 444 registeredActions.push(actionData);
446 445 }
447 446 });
448 447
449 448 return function(cm, pred) {
450 449 var cur = cm.getCursor();
451 450 var options = {
452 451 closeOnUnfocus: true,
453 452 registeredActions: registeredActions
454 453 };
455 454 setTimeout(function() {
456 455 if (!cm.state.completionActive) {
457 456 cmLog.debug('Trigger actions hinting');
458 457 CodeMirror.showHint(cm, CodeMirror.hint.actions, options);
459 458 }
460 459 }, 100);
461 460
462 461 // tell CodeMirror we didn't handle the key
463 462 // trick to trigger on a char but still complete it
464 463 return CodeMirror.Pass;
465 464 }
466 465 };
467 466
468 467 var extraKeys = {
469 468 "'@'": CodeMirrorCompleteAfter,
470 469 Tab: function(cm) {
471 470 // space indent instead of TABS
472 471 var spaces = new Array(cm.getOption("indentUnit") + 1).join(" ");
473 472 cm.replaceSelection(spaces);
474 473 }
475 474 };
476 475 // submit form on Meta-Enter
477 476 if (OSType === "mac") {
478 477 extraKeys["Cmd-Enter"] = submitForm;
479 478 }
480 479 else {
481 480 extraKeys["Ctrl-Enter"] = submitForm;
482 481 }
483 482
484 483 if (triggerActions) {
485 484 // register triggerActions for this instance
486 485 extraKeys["'/'"] = completeActions(triggerActions);
487 486 }
488 487
489 488 var cm = CodeMirror.fromTextArea($(textAreaId).get(0), {
490 489 lineNumbers: false,
491 490 indentUnit: 4,
492 491 viewportMargin: 30,
493 492 // this is a trick to trigger some logic behind codemirror placeholder
494 493 // it influences styling and behaviour.
495 494 placeholder: " ",
496 495 extraKeys: extraKeys,
497 496 lineWrapping: true
498 497 });
499 498
500 499 cm.setSize(null, initialHeight);
501 500 cm.setOption("mode", DEFAULT_RENDERER);
502 501 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
503 502 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
503
504 504 // start listening on changes to make auto-expanded editor
505 cm.on("change", function(self) {
505 cm.on("change", function (self) {
506 506 var height = initialHeight;
507 507 var lines = self.lineCount();
508 if ( lines > 6 && lines < 20) {
508 if (lines > 6 && lines < 20) {
509 509 height = "auto";
510 }
511 else if (lines >= 20){
512 zheight = 20*15;
510 } else if (lines >= 20) {
511 height = 20 * 15;
513 512 }
514 513 self.setSize(null, height);
515 514 });
516 515
517 516 var actionHint = function(editor, options) {
518 517
519 518 var cur = editor.getCursor();
520 519 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
521 520
522 521 // match only on /+1 character minimum
523 522 var tokenMatch = new RegExp('(^/\|/\)([a-zA-Z]*)$').exec(curLine);
524 523
525 524 var tokenStr = '';
526 525 if (tokenMatch !== null && tokenMatch.length > 0){
527 526 tokenStr = tokenMatch[2].strip();
528 527 }
529 528
530 529 var context = {
531 530 start: (cur.ch - tokenStr.length) - 1,
532 531 end: cur.ch,
533 532 string: tokenStr,
534 533 type: null
535 534 };
536 535
537 536 return {
538 537 list: filterActions(options.registeredActions, context),
539 538 from: CodeMirror.Pos(cur.line, context.start),
540 539 to: CodeMirror.Pos(cur.line, context.end)
541 540 };
542 541
543 542 };
544 543 CodeMirror.registerHelper("hint", "mentions", CodeMirrorMentionHint);
545 544 CodeMirror.registerHelper("hint", "actions", actionHint);
546 545 return cm;
547 546 };
548 547
549 548 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
550 549 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
551 550 codeMirrorInstance.setOption("mode", mode);
552 551 };
553 552
554 553 var setCodeMirrorLineWrap = function(codeMirrorInstance, line_wrap) {
555 554 codeMirrorInstance.setOption("lineWrapping", line_wrap);
556 555 };
557 556
558 557 var setCodeMirrorModeFromSelect = function(
559 558 targetSelect, targetFileInput, codeMirrorInstance, callback){
560 559
561 560 $(targetSelect).on('change', function(e) {
562 561 cmLog.debug('codemirror select2 mode change event !');
563 562 var selected = e.currentTarget;
564 563 var node = selected.options[selected.selectedIndex];
565 564 var mimetype = node.value;
566 565 cmLog.debug('picked mimetype', mimetype);
567 566 var new_mode = $(node).attr('mode');
568 567 setCodeMirrorMode(codeMirrorInstance, new_mode);
569 568 cmLog.debug('set new mode', new_mode);
570 569
571 570 //propose filename from picked mode
572 571 cmLog.debug('setting mimetype', mimetype);
573 572 var proposed_ext = getExtFromMimeType(mimetype);
574 573 cmLog.debug('file input', $(targetFileInput).val());
575 574 var file_data = getFilenameAndExt($(targetFileInput).val());
576 575 var filename = file_data.filename || 'filename1';
577 576 $(targetFileInput).val(filename + proposed_ext);
578 577 cmLog.debug('proposed file', filename + proposed_ext);
579 578
580 579
581 580 if (typeof(callback) === 'function') {
582 581 try {
583 582 cmLog.debug('running callback', callback);
584 583 callback(filename, mimetype, new_mode);
585 584 } catch (err) {
586 585 console.log('failed to run callback', callback, err);
587 586 }
588 587 }
589 588 cmLog.debug('finish iteration...');
590 589 });
591 590 };
592 591
593 592 var setCodeMirrorModeFromInput = function(
594 593 targetSelect, targetFileInput, codeMirrorInstance, callback) {
595 594
596 595 // on type the new filename set mode
597 596 $(targetFileInput).on('keyup', function(e) {
598 597 var file_data = getFilenameAndExt(this.value);
599 598 if (file_data.ext === null) {
600 599 return;
601 600 }
602 601
603 602 var mimetypes = getMimeTypeFromExt(file_data.ext, true);
604 603 cmLog.debug('mimetype from file', file_data, mimetypes);
605 604 var detected_mode;
606 605 var detected_option;
607 606 for (var i in mimetypes) {
608 607 var mt = mimetypes[i];
609 608 if (!detected_mode) {
610 609 detected_mode = detectCodeMirrorMode(this.value, mt);
611 610 }
612 611
613 612 if (!detected_option) {
614 613 cmLog.debug('#mimetype option[value="{0}"]'.format(mt));
615 614 if ($(targetSelect).find('option[value="{0}"]'.format(mt)).length) {
616 615 detected_option = mt;
617 616 }
618 617 }
619 618 }
620 619
621 620 cmLog.debug('detected mode', detected_mode);
622 621 cmLog.debug('detected option', detected_option);
623 622 if (detected_mode && detected_option){
624 623
625 624 $(targetSelect).select2("val", detected_option);
626 625 setCodeMirrorMode(codeMirrorInstance, detected_mode);
627 626
628 627 if(typeof(callback) === 'function'){
629 628 try{
630 629 cmLog.debug('running callback', callback);
631 630 var filename = file_data.filename + "." + file_data.ext;
632 631 callback(filename, detected_option, detected_mode);
633 632 }catch (err){
634 633 console.log('failed to run callback', callback, err);
635 634 }
636 635 }
637 636 }
638 637
639 638 });
640 639 };
641 640
642 641 var fillCodeMirrorOptions = function(targetSelect) {
643 642 //inject new modes, based on codeMirrors modeInfo object
644 643 var modes_select = $(targetSelect);
645 644 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
646 645 var m = CodeMirror.modeInfo[i];
647 646 var opt = new Option(m.name, m.mime);
648 647 $(opt).attr('mode', m.mode);
649 648 modes_select.append(opt);
650 649 }
651 650 };
652 651
653 652
654 653 /* markup form */
655 654 (function(mod) {
656 655
657 656 if (typeof exports == "object" && typeof module == "object") {
658 657 // CommonJS
659 658 module.exports = mod();
660 659 }
661 660 else {
662 661 // Plain browser env
663 662 (this || window).MarkupForm = mod();
664 663 }
665 664
666 665 })(function() {
667 666 "use strict";
668 667
669 668 function MarkupForm(textareaId) {
670 669 if (!(this instanceof MarkupForm)) {
671 670 return new MarkupForm(textareaId);
672 671 }
673 672
674 673 // bind the element instance to our Form
675 674 $('#' + textareaId).get(0).MarkupForm = this;
676 675
677 676 this.withSelectorId = function(selector) {
678 677 var selectorId = textareaId;
679 678 return selector + '_' + selectorId;
680 679 };
681 680
682 681 this.previewButton = this.withSelectorId('#preview-btn');
683 682 this.previewContainer = this.withSelectorId('#preview-container');
684 683
685 684 this.previewBoxSelector = this.withSelectorId('#preview-box');
686 685
687 686 this.editButton = this.withSelectorId('#edit-btn');
688 687 this.editContainer = this.withSelectorId('#edit-container');
689 688
690 689 this.cmBox = textareaId;
691 690 this.cm = initMarkupCodeMirror('#' + textareaId);
692 691
693 692 this.previewUrl = pyroutes.url('markup_preview');
694 693
695 694 // FUNCTIONS and helpers
696 695 var self = this;
697 696
698 697 this.getCmInstance = function(){
699 698 return this.cm
700 699 };
701 700
702 701 this.setPlaceholder = function(placeholder) {
703 702 var cm = this.getCmInstance();
704 703 if (cm){
705 704 cm.setOption('placeholder', placeholder);
706 705 }
707 706 };
708 707
709 708 this.initStatusChangeSelector = function(){
710 709 var formatChangeStatus = function(state, escapeMarkup) {
711 710 var originalOption = state.element;
712 711 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
713 712 return tmpl
714 713 };
715 714 var formatResult = function(result, container, query, escapeMarkup) {
716 715 return formatChangeStatus(result, escapeMarkup);
717 716 };
718 717
719 718 var formatSelection = function(data, container, escapeMarkup) {
720 719 return formatChangeStatus(data, escapeMarkup);
721 720 };
722 721
723 722 $(this.submitForm).find(this.statusChange).select2({
724 723 placeholder: _gettext('Status Review'),
725 724 formatResult: formatResult,
726 725 formatSelection: formatSelection,
727 726 containerCssClass: "drop-menu status_box_menu",
728 727 dropdownCssClass: "drop-menu-dropdown",
729 728 dropdownAutoWidth: true,
730 729 minimumResultsForSearch: -1
731 730 });
732 731 $(this.submitForm).find(this.statusChange).on('change', function() {
733 732 var status = self.getCommentStatus();
734 733
735 734 if (status && !self.isInline()) {
736 735 $(self.submitButton).prop('disabled', false);
737 736 }
738 737
739 738 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
740 739 self.setPlaceholder(placeholderText)
741 740 })
742 741 };
743 742
744 743 // reset the text area into it's original state
745 744 this.resetMarkupFormState = function(content) {
746 745 content = content || '';
747 746
748 747 $(this.editContainer).show();
749 748 $(this.editButton).parent().addClass('active');
750 749
751 750 $(this.previewContainer).hide();
752 751 $(this.previewButton).parent().removeClass('active');
753 752
754 753 this.setActionButtonsDisabled(true);
755 754 self.cm.setValue(content);
756 755 self.cm.setOption("readOnly", false);
757 756 };
758 757
759 758 this.previewSuccessCallback = function(o) {
760 759 $(self.previewBoxSelector).html(o);
761 760 $(self.previewBoxSelector).removeClass('unloaded');
762 761
763 762 // swap buttons, making preview active
764 763 $(self.previewButton).parent().addClass('active');
765 764 $(self.editButton).parent().removeClass('active');
766 765
767 766 // unlock buttons
768 767 self.setActionButtonsDisabled(false);
769 768 };
770 769
771 770 this.setActionButtonsDisabled = function(state) {
772 771 $(this.editButton).prop('disabled', state);
773 772 $(this.previewButton).prop('disabled', state);
774 773 };
775 774
776 775 // lock preview/edit/submit buttons on load, but exclude cancel button
777 776 var excludeCancelBtn = true;
778 777 this.setActionButtonsDisabled(true);
779 778
780 779 // anonymous users don't have access to initialized CM instance
781 780 if (this.cm !== undefined){
782 781 this.cm.on('change', function(cMirror) {
783 782 if (cMirror.getValue() === "") {
784 783 self.setActionButtonsDisabled(true)
785 784 } else {
786 785 self.setActionButtonsDisabled(false)
787 786 }
788 787 });
789 788 }
790 789
791 790 $(this.editButton).on('click', function(e) {
792 791 e.preventDefault();
793 792
794 793 $(self.previewButton).parent().removeClass('active');
795 794 $(self.previewContainer).hide();
796 795
797 796 $(self.editButton).parent().addClass('active');
798 797 $(self.editContainer).show();
799 798
800 799 });
801 800
802 801 $(this.previewButton).on('click', function(e) {
803 802 e.preventDefault();
804 803 var text = self.cm.getValue();
805 804
806 805 if (text === "") {
807 806 return;
808 807 }
809 808
810 809 var postData = {
811 810 'text': text,
812 811 'renderer': templateContext.visual.default_renderer,
813 812 'csrf_token': CSRF_TOKEN
814 813 };
815 814
816 815 // lock ALL buttons on preview
817 816 self.setActionButtonsDisabled(true);
818 817
819 818 $(self.previewBoxSelector).addClass('unloaded');
820 819 $(self.previewBoxSelector).html(_gettext('Loading ...'));
821 820
822 821 $(self.editContainer).hide();
823 822 $(self.previewContainer).show();
824 823
825 824 // by default we reset state of comment preserving the text
826 825 var previewFailCallback = function(data){
827 826 alert(
828 827 "Error while submitting preview.\n" +
829 828 "Error code {0} ({1}).".format(data.status, data.statusText)
830 829 );
831 830 self.resetMarkupFormState(text)
832 831 };
833 832 _submitAjaxPOST(
834 833 self.previewUrl, postData, self.previewSuccessCallback,
835 834 previewFailCallback);
836 835
837 836 $(self.previewButton).parent().addClass('active');
838 837 $(self.editButton).parent().removeClass('active');
839 838 });
840 839
841 840 }
842 841
843 842 return MarkupForm;
844 843 });
General Comments 0
You need to be logged in to leave comments. Login now