##// END OF EJS Templates
Added server side file editing with commit
marcink -
r1305:166317d4 beta
parent child Browse files
Show More
@@ -0,0 +1,54 b''
1 .CodeMirror {
2 overflow: auto;
3 height: 450px;
4 line-height: 1em;
5 font-family: monospace;
6 _position: relative; /* IE6 hack */
7 }
8
9 .CodeMirror-gutter {
10 position: absolute; left: 0; top: 0;
11 background-color: #f7f7f7;
12 border-right: 1px solid #eee;
13 min-width: 2em;
14 height: 100%;
15 }
16 .CodeMirror-gutter-text {
17 color: #aaa;
18 text-align: right;
19 padding: .4em .2em .4em .4em;
20 }
21 .CodeMirror-lines {
22 padding: .4em;
23 }
24
25 .CodeMirror pre {
26 -moz-border-radius: 0;
27 -webkit-border-radius: 0;
28 -o-border-radius: 0;
29 border-radius: 0;
30 border-width: 0; margin: 0; padding: 0; background: transparent;
31 font-family: inherit;
32 }
33
34 .CodeMirror-cursor {
35 z-index: 10;
36 position: absolute;
37 visibility: hidden;
38 border-left: 1px solid black !important;
39 }
40 .CodeMirror-focused .CodeMirror-cursor {
41 visibility: visible;
42 }
43
44 span.CodeMirror-selected {
45 background: #ccc !important;
46 color: HighlightText !important;
47 }
48 .CodeMirror-focused span.CodeMirror-selected {
49 background: Highlight !important;
50 }
51
52 .CodeMirror-matchingbracket {color: #0f0 !important;}
53 .CodeMirror-nonmatchingbracket {color: #f22 !important;}
54 .CodeMirror-gutter-text{color: #003367 !important;} No newline at end of file
This diff has been collapsed as it changes many lines, (1915 lines changed) Show them Hide them
@@ -0,0 +1,1915 b''
1 // All functions that need access to the editor's state live inside
2 // the CodeMirror function. Below that, at the bottom of the file,
3 // some utilities are defined.
4
5 // CodeMirror is the only global var we claim
6 var CodeMirror = (function() {
7 // This is the function that produces an editor instance. It's
8 // closure is used to store the editor state.
9 function CodeMirror(place, givenOptions) {
10 // Determine effective options based on given values and defaults.
11 var options = {}, defaults = CodeMirror.defaults;
12 for (var opt in defaults)
13 if (defaults.hasOwnProperty(opt))
14 options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
15
16 // The element in which the editor lives. Takes care of scrolling
17 // (if enabled).
18 var wrapper = document.createElement("div");
19 wrapper.className = "CodeMirror";
20 // This mess creates the base DOM structure for the editor.
21 wrapper.innerHTML =
22 '<div style="position: relative">' + // Set to the height of the text, causes scrolling
23 '<pre style="position: relative; height: 0; visibility: hidden; overflow: hidden;">' + // To measure line/char size
24 '<span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></pre>' +
25 '<div style="position: relative">' + // Moved around its parent to cover visible view
26 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
27 '<div style="overflow: hidden; position: absolute; width: 0; left: 0">' + // Wraps and hides input textarea
28 '<textarea style="height: 1px; position: absolute; width: 1px;" wrap="off"></textarea></div>' +
29 // Provides positioning relative to (visible) text origin
30 '<div class="CodeMirror-lines"><div style="position: relative">' +
31 '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
32 '<div></div></div></div></div></div>'; // This DIV contains the actual code
33 if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
34 // I've never seen more elegant code in my life.
35 var code = wrapper.firstChild, measure = code.firstChild, mover = measure.nextSibling,
36 gutter = mover.firstChild, gutterText = gutter.firstChild,
37 inputDiv = gutter.nextSibling, input = inputDiv.firstChild,
38 lineSpace = inputDiv.nextSibling.firstChild, cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
39 if (options.tabindex != null) input.tabindex = options.tabindex;
40 if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
41
42 // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
43 var poll = new Delayed(), highlight = new Delayed(), blinker;
44
45 // mode holds a mode API object. lines an array of Line objects
46 // (see Line constructor), work an array of lines that should be
47 // parsed, and history the undo history (instance of History
48 // constructor).
49 var mode, lines = [new Line("")], work, history = new History(), focused;
50 loadMode();
51 // The selection. These are always maintained to point at valid
52 // positions. Inverted is used to remember that the user is
53 // selecting bottom-to-top.
54 var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
55 // Selection-related flags. shiftSelecting obviously tracks
56 // whether the user is holding shift. reducedSelection is a hack
57 // to get around the fact that we can't create inverted
58 // selections. See below.
59 var shiftSelecting, reducedSelection;
60 // Variables used by startOperation/endOperation to track what
61 // happened during the operation.
62 var updateInput, changes, textChanged, selectionChanged, leaveInputAlone;
63 // Current visible range (may be bigger than the view window).
64 var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
65 // editing will hold an object describing the things we put in the
66 // textarea, to help figure out whether something changed.
67 // bracketHighlighted is used to remember that a backet has been
68 // marked.
69 var editing, bracketHighlighted;
70
71 // Initialize the content. Somewhat hacky (delayed prepareInput)
72 // to work around browser issues.
73 operation(function(){setValue(options.value || ""); updateInput = false;})();
74 setTimeout(prepareInput, 20);
75
76 // Register our event handlers.
77 connect(wrapper, "mousedown", operation(onMouseDown));
78 // Gecko browsers fire contextmenu *after* opening the menu, at
79 // which point we can't mess with it anymore. Context menu is
80 // handled in onMouseDown for Gecko.
81 if (!gecko) connect(wrapper, "contextmenu", operation(onContextMenu));
82 connect(code, "dblclick", operation(onDblClick));
83 connect(wrapper, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);});
84 connect(window, "resize", function() {updateDisplay(true);});
85 connect(input, "keyup", operation(onKeyUp));
86 connect(input, "keydown", operation(onKeyDown));
87 connect(input, "keypress", operation(onKeyPress));
88 connect(input, "focus", onFocus);
89 connect(input, "blur", onBlur);
90
91 connect(wrapper, "dragenter", function(e){e.stop();});
92 connect(wrapper, "dragover", function(e){e.stop();});
93 connect(wrapper, "drop", operation(onDrop));
94 connect(wrapper, "paste", function(){input.focus(); fastPoll();});
95 connect(input, "paste", function(){fastPoll();});
96 connect(input, "cut", function(){fastPoll();});
97
98 if (document.activeElement == input) onFocus();
99 else onBlur();
100
101 function isLine(l) {return l >= 0 && l < lines.length;}
102 // The instance object that we'll return. Mostly calls out to
103 // local functions in the CodeMirror function. Some do some extra
104 // range checking and/or clipping. operation is used to wrap the
105 // call so that changes it makes are tracked, and the display is
106 // updated afterwards.
107 var instance = {
108 getValue: getValue,
109 setValue: operation(setValue),
110 getSelection: getSelection,
111 replaceSelection: operation(replaceSelection),
112 focus: function(){input.focus(); onFocus(); fastPoll();},
113 setOption: function(option, value) {
114 options[option] = value;
115 if (option == "lineNumbers" || option == "gutter") gutterChanged();
116 else if (option == "mode" || option == "indentUnit") loadMode();
117 },
118 getOption: function(option) {return options[option];},
119 undo: operation(undo),
120 redo: operation(redo),
121 indentLine: operation(function(n) {if (isLine(n)) indentLine(n, "smart");}),
122 historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
123 matchBrackets: operation(function(){matchBrackets(true);}),
124 getTokenAt: function(pos) {
125 pos = clipPos(pos);
126 return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
127 },
128 cursorCoords: function(start){
129 if (start == null) start = sel.inverted;
130 return pageCoords(start ? sel.from : sel.to);
131 },
132 charCoords: function(pos){return pageCoords(clipPos(pos));},
133 coordsChar: function(coords) {
134 var off = eltOffset(lineSpace);
135 var line = Math.min(showingTo - 1, showingFrom + Math.floor(coords.y / lineHeight()));
136 return clipPos({line: line, ch: charFromX(clipLine(line), coords.x)});
137 },
138 getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
139 markText: operation(function(a, b, c){return operation(markText(a, b, c));}),
140 setMarker: addGutterMarker,
141 clearMarker: removeGutterMarker,
142 setLineClass: operation(setLineClass),
143 lineInfo: lineInfo,
144 addWidget: function(pos, node, scroll) {
145 var pos = localCoords(clipPos(pos), true);
146 node.style.top = (showingFrom * lineHeight() + pos.yBot + paddingTop()) + "px";
147 node.style.left = (pos.x + paddingLeft()) + "px";
148 code.appendChild(node);
149 if (scroll)
150 scrollIntoView(pos.x, pos.yBot, pos.x + node.offsetWidth, pos.yBot + node.offsetHeight);
151 },
152
153 lineCount: function() {return lines.length;},
154 getCursor: function(start) {
155 if (start == null) start = sel.inverted;
156 return copyPos(start ? sel.from : sel.to);
157 },
158 somethingSelected: function() {return !posEq(sel.from, sel.to);},
159 setCursor: operation(function(line, ch) {
160 if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch);
161 else setCursor(line, ch);
162 }),
163 setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
164 getLine: function(line) {if (isLine(line)) return lines[line].text;},
165 setLine: operation(function(line, text) {
166 if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length});
167 }),
168 removeLine: operation(function(line) {
169 if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
170 }),
171 replaceRange: operation(replaceRange),
172 getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
173
174 operation: function(f){return operation(f)();},
175 refresh: function(){updateDisplay(true);},
176 getInputField: function(){return input;},
177 getWrapperElement: function(){return wrapper;}
178 };
179
180 function setValue(code) {
181 history = null;
182 var top = {line: 0, ch: 0};
183 updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
184 splitLines(code), top, top);
185 history = new History();
186 }
187 function getValue(code) {
188 var text = [];
189 for (var i = 0, l = lines.length; i < l; ++i)
190 text.push(lines[i].text);
191 return text.join("\n");
192 }
193
194 function onMouseDown(e) {
195 // First, see if this is a click in the gutter
196 for (var n = e.target(); n != wrapper; n = n.parentNode)
197 if (n.parentNode == gutterText) {
198 if (options.onGutterClick)
199 options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom);
200 return e.stop();
201 }
202
203 if (gecko && e.button() == 3) onContextMenu(e);
204 if (e.button() != 1) return;
205 // For button 1, if it was clicked inside the editor
206 // (posFromMouse returning non-null), we have to adjust the
207 // selection.
208 var start = posFromMouse(e), last = start, going;
209 if (!start) {if (e.target() == wrapper) e.stop(); return;}
210 setCursor(start.line, start.ch, false);
211
212 if (!focused) onFocus();
213 e.stop();
214 // And then we have to see if it's a drag event, in which case
215 // the dragged-over text must be selected.
216 function end() {
217 input.focus();
218 updateInput = true;
219 move(); up();
220 }
221 function extend(e) {
222 var cur = posFromMouse(e, true);
223 if (cur && !posEq(cur, last)) {
224 if (!focused) onFocus();
225 last = cur;
226 setSelection(start, cur);
227 updateInput = false;
228 var visible = visibleLines();
229 if (cur.line >= visible.to || cur.line < visible.from)
230 going = setTimeout(operation(function(){extend(e);}), 150);
231 }
232 }
233
234 var move = connect(document, "mousemove", operation(function(e) {
235 clearTimeout(going);
236 e.stop();
237 extend(e);
238 }), true);
239 var up = connect(document, "mouseup", operation(function(e) {
240 clearTimeout(going);
241 var cur = posFromMouse(e);
242 if (cur) setSelection(start, cur);
243 e.stop();
244 end();
245 }), true);
246 }
247 function onDblClick(e) {
248 var pos = posFromMouse(e);
249 if (!pos) return;
250 selectWordAt(pos);
251 e.stop();
252 }
253 function onDrop(e) {
254 var pos = posFromMouse(e, true), files = e.e.dataTransfer.files;
255 if (!pos || options.readOnly) return;
256 if (files && files.length && window.FileReader && window.File) {
257 var n = files.length, text = Array(n), read = 0;
258 for (var i = 0; i < n; ++i) loadFile(files[i], i);
259 function loadFile(file, i) {
260 var reader = new FileReader;
261 reader.onload = function() {
262 text[i] = reader.result;
263 if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos));
264 };
265 reader.readAsText(file);
266 }
267 }
268 else {
269 try {
270 var text = e.e.dataTransfer.getData("Text");
271 if (text) replaceRange(text, pos, pos);
272 }
273 catch(e){}
274 }
275 }
276 function onKeyDown(e) {
277 if (!focused) onFocus();
278
279 var code = e.e.keyCode;
280 // Tries to detect ctrl on non-mac, cmd on mac.
281 var mod = (mac ? e.e.metaKey : e.e.ctrlKey) && !e.e.altKey, anyMod = e.e.ctrlKey || e.e.altKey || e.e.metaKey;
282 if (code == 16 || e.e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
283 else shiftSelecting = null;
284 // First give onKeyEvent option a chance to handle this.
285 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
286
287 if (code == 33 || code == 34) {scrollPage(code == 34); return e.stop();} // page up/down
288 if (mod && (code == 36 || code == 35)) {scrollEnd(code == 36); return e.stop();} // ctrl-home/end
289 if (mod && code == 65) {selectAll(); return e.stop();} // ctrl-a
290 if (!options.readOnly) {
291 if (!anyMod && code == 13) {return;} // enter
292 if (!anyMod && code == 9 && handleTab(e.e.shiftKey)) return e.stop(); // tab
293 if (mod && code == 90) {undo(); return e.stop();} // ctrl-z
294 if (mod && ((e.e.shiftKey && code == 90) || code == 89)) {redo(); return e.stop();} // ctrl-shift-z, ctrl-y
295 }
296
297 // Key id to use in the movementKeys map. We also pass it to
298 // fastPoll in order to 'self learn'. We need this because
299 // reducedSelection, the hack where we collapse the selection to
300 // its start when it is inverted and a movement key is pressed
301 // (and later restore it again), shouldn't be used for
302 // non-movement keys.
303 curKeyId = (mod ? "c" : "") + code;
304 if (sel.inverted && movementKeys.hasOwnProperty(curKeyId)) {
305 var range = selRange(input);
306 if (range) {
307 reducedSelection = {anchor: range.start};
308 setSelRange(input, range.start, range.start);
309 }
310 }
311 fastPoll(curKeyId);
312 }
313 function onKeyUp(e) {
314 if (reducedSelection) {
315 reducedSelection = null;
316 updateInput = true;
317 }
318 if (e.e.keyCode == 16) shiftSelecting = null;
319 }
320 function onKeyPress(e) {
321 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
322 if (options.electricChars && mode.electricChars) {
323 var ch = String.fromCharCode(e.e.charCode == null ? e.e.keyCode : e.e.charCode);
324 if (mode.electricChars.indexOf(ch) > -1)
325 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
326 }
327 var code = e.e.keyCode;
328 // Re-stop tab and enter. Necessary on some browsers.
329 if (code == 13) {handleEnter(); e.stop();}
330 else if (code == 9 && options.tabMode != "default") e.stop();
331 else fastPoll(curKeyId);
332 }
333
334 function onFocus() {
335 if (!focused && options.onFocus) options.onFocus(instance);
336 focused = true;
337 slowPoll();
338 if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
339 wrapper.className += " CodeMirror-focused";
340 restartBlink();
341 }
342 function onBlur() {
343 if (focused && options.onBlur) options.onBlur(instance);
344 clearInterval(blinker);
345 shiftSelecting = null;
346 focused = false;
347 wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
348 }
349
350 // Replace the range from from to to by the strings in newText.
351 // Afterwards, set the selection to selFrom, selTo.
352 function updateLines(from, to, newText, selFrom, selTo) {
353 if (history) {
354 var old = [];
355 for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text);
356 history.addChange(from.line, newText.length, old);
357 while (history.done.length > options.undoDepth) history.done.shift();
358 }
359 updateLinesNoUndo(from, to, newText, selFrom, selTo);
360 }
361 function unredoHelper(from, to) {
362 var change = from.pop();
363 if (change) {
364 var replaced = [], end = change.start + change.added;
365 for (var i = change.start; i < end; ++i) replaced.push(lines[i].text);
366 to.push({start: change.start, added: change.old.length, old: replaced});
367 var pos = clipPos({line: change.start + change.old.length - 1,
368 ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
369 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
370 }
371 }
372 function undo() {unredoHelper(history.done, history.undone);}
373 function redo() {unredoHelper(history.undone, history.done);}
374
375 function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
376 var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
377 // First adjust the line structure, taking some care to leave highlighting intact.
378 if (firstLine == lastLine) {
379 if (newText.length == 1)
380 firstLine.replace(from.ch, to.ch, newText[0]);
381 else {
382 lastLine = firstLine.split(to.ch, newText[newText.length-1]);
383 var spliceargs = [from.line + 1, nlines];
384 firstLine.replace(from.ch, firstLine.text.length, newText[0]);
385 for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
386 spliceargs.push(lastLine);
387 lines.splice.apply(lines, spliceargs);
388 }
389 }
390 else if (newText.length == 1) {
391 firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
392 lines.splice(from.line + 1, nlines);
393 }
394 else {
395 var spliceargs = [from.line + 1, nlines - 1];
396 firstLine.replace(from.ch, firstLine.text.length, newText[0]);
397 lastLine.replace(0, to.ch, newText[newText.length-1]);
398 for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
399 lines.splice.apply(lines, spliceargs);
400 }
401
402 // Add these lines to the work array, so that they will be
403 // highlighted. Adjust work lines if lines were added/removed.
404 var newWork = [], lendiff = newText.length - nlines - 1;
405 for (var i = 0, l = work.length; i < l; ++i) {
406 var task = work[i];
407 if (task < from.line) newWork.push(task);
408 else if (task > to.line) newWork.push(task + lendiff);
409 }
410 if (newText.length) newWork.push(from.line);
411 work = newWork;
412 startWorker(100);
413 // Remember that these lines changed, for updating the display
414 changes.push({from: from.line, to: to.line + 1, diff: lendiff});
415 textChanged = true;
416
417 // Update the selection
418 function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
419 setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
420
421 // Make sure the scroll-size div has the correct height.
422 code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
423 }
424
425 function replaceRange(code, from, to) {
426 from = clipPos(from);
427 if (!to) to = from; else to = clipPos(to);
428 code = splitLines(code);
429 function adjustPos(pos) {
430 if (posLess(pos, from)) return pos;
431 if (!posLess(to, pos)) return end;
432 var line = pos.line + code.length - (to.line - from.line) - 1;
433 var ch = pos.ch;
434 if (pos.line == to.line)
435 ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));
436 return {line: line, ch: ch};
437 }
438 var end;
439 replaceRange1(code, from, to, function(end1) {
440 end = end1;
441 return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
442 });
443 return end;
444 }
445 function replaceSelection(code, collapse) {
446 replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
447 if (collapse == "end") return {from: end, to: end};
448 else if (collapse == "start") return {from: sel.from, to: sel.from};
449 else return {from: sel.from, to: end};
450 });
451 }
452 function replaceRange1(code, from, to, computeSel) {
453 var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;
454 var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
455 updateLines(from, to, code, newSel.from, newSel.to);
456 }
457
458 function getRange(from, to) {
459 var l1 = from.line, l2 = to.line;
460 if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch);
461 var code = [lines[l1].text.slice(from.ch)];
462 for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text);
463 code.push(lines[l2].text.slice(0, to.ch));
464 return code.join("\n");
465 }
466 function getSelection() {
467 return getRange(sel.from, sel.to);
468 }
469
470 var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
471 function slowPoll() {
472 if (pollingFast) return;
473 poll.set(2000, function() {
474 startOperation();
475 readInput();
476 if (focused) slowPoll();
477 endOperation();
478 });
479 }
480 function fastPoll(keyId) {
481 var missed = false;
482 pollingFast = true;
483 function p() {
484 startOperation();
485 var changed = readInput();
486 if (changed == "moved" && keyId) movementKeys[keyId] = true;
487 if (!changed && !missed) {missed = true; poll.set(80, p);}
488 else {pollingFast = false; slowPoll();}
489 endOperation();
490 }
491 poll.set(20, p);
492 }
493
494 // Inspects the textarea, compares its state (content, selection)
495 // to the data in the editing variable, and updates the editor
496 // content or cursor if something changed.
497 function readInput() {
498 var changed = false, text = input.value, sr = selRange(input);
499 if (!sr) return false;
500 var changed = editing.text != text, rs = reducedSelection;
501 var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end);
502 if (reducedSelection && !moved && sel.from.line == 0 && sel.from.ch == 0)
503 reducedSelection = null;
504 else if (!moved) return false;
505 if (changed) {
506 shiftSelecting = reducedSelection = null;
507 if (options.readOnly) {updateInput = true; return "changed";}
508 }
509
510 // Compute selection start and end based on start/end offsets in textarea
511 function computeOffset(n, startLine) {
512 var pos = 0;
513 for (;;) {
514 var found = text.indexOf("\n", pos);
515 if (found == -1 || (text.charAt(found-1) == "\r" ? found - 1 : found) >= n)
516 return {line: startLine, ch: n - pos};
517 ++startLine;
518 pos = found + 1;
519 }
520 }
521 var from = computeOffset(sr.start, editing.from),
522 to = computeOffset(sr.end, editing.from);
523 // Here we have to take the reducedSelection hack into account,
524 // so that you can, for example, press shift-up at the start of
525 // your selection and have the right thing happen.
526 if (rs) {
527 from = sr.start == rs.anchor ? to : from;
528 to = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
529 if (!posLess(from, to)) {
530 reducedSelection = null;
531 sel.inverted = false;
532 var tmp = from; from = to; to = tmp;
533 }
534 }
535
536 // In some cases (cursor on same line as before), we don't have
537 // to update the textarea content at all.
538 if (from.line == to.line && from.line == sel.from.line && from.line == sel.to.line && !shiftSelecting)
539 updateInput = false;
540
541 // Magic mess to extract precise edited range from the changed
542 // string.
543 if (changed) {
544 var start = 0, end = text.length, len = Math.min(end, editing.text.length);
545 var c, line = editing.from, nl = -1;
546 while (start < len && (c = text.charAt(start)) == editing.text.charAt(start)) {
547 ++start;
548 if (c == "\n") {line++; nl = start;}
549 }
550 var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length;
551 for (;;) {
552 c = editing.text.charAt(edend);
553 if (c == "\n") endline--;
554 if (text.charAt(end) != c) {++end; ++edend; break;}
555 if (edend <= start || end <= start) break;
556 --end; --edend;
557 }
558 var nl = editing.text.lastIndexOf("\n", edend - 1), endch = nl == -1 ? edend : edend - nl - 1;
559 updateLines({line: line, ch: ch}, {line: endline, ch: endch}, splitLines(text.slice(start, end)), from, to);
560 if (line != endline || from.line != line) updateInput = true;
561 }
562 else setSelection(from, to);
563
564 editing.text = text; editing.start = sr.start; editing.end = sr.end;
565 return changed ? "changed" : moved ? "moved" : false;
566 }
567
568 // Set the textarea content and selection range to match the
569 // editor state.
570 function prepareInput() {
571 var text = [];
572 var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2);
573 for (var i = from; i < to; ++i) text.push(lines[i].text);
574 text = input.value = text.join(lineSep);
575 var startch = sel.from.ch, endch = sel.to.ch;
576 for (var i = from; i < sel.from.line; ++i)
577 startch += lineSep.length + lines[i].text.length;
578 for (var i = from; i < sel.to.line; ++i)
579 endch += lineSep.length + lines[i].text.length;
580 editing = {text: text, from: from, to: to, start: startch, end: endch};
581 setSelRange(input, startch, reducedSelection ? startch : endch);
582 }
583
584 function scrollCursorIntoView() {
585 var cursor = localCoords(sel.inverted ? sel.from : sel.to);
586 return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
587 }
588 function scrollIntoView(x1, y1, x2, y2) {
589 var pl = paddingLeft(), pt = paddingTop();
590 y1 += pt; y2 += pt; x1 += pl; x2 += pl;
591 var screen = wrapper.clientHeight, screentop = wrapper.scrollTop, scrolled = false, result = true;
592 if (y1 < screentop) {wrapper.scrollTop = Math.max(0, y1 - 10); scrolled = true;}
593 else if (y2 > screentop + screen) {wrapper.scrollTop = y2 + 10 - screen; scrolled = true;}
594
595 var screenw = wrapper.clientWidth, screenleft = wrapper.scrollLeft;
596 if (x1 < screenleft) {wrapper.scrollLeft = Math.max(0, x1 - 10); scrolled = true;}
597 else if (x2 > screenw + screenleft) {
598 wrapper.scrollLeft = x2 + 10 - screenw;
599 scrolled = true;
600 if (x2 > code.clientWidth) result = false;
601 }
602 if (scrolled && options.onScroll) options.onScroll(instance);
603 return result;
604 }
605
606 function visibleLines() {
607 var lh = lineHeight(), top = wrapper.scrollTop - paddingTop();
608 return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
609 to: Math.min(lines.length, Math.ceil((top + wrapper.clientHeight) / lh))};
610 }
611 // Uses a set of changes plus the current scroll position to
612 // determine which DOM updates have to be made, and makes the
613 // updates.
614 function updateDisplay(changes) {
615 if (!wrapper.clientWidth) {
616 showingFrom = showingTo = 0;
617 return;
618 }
619 // First create a range of theoretically intact lines, and punch
620 // holes in that using the change info.
621 var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}];
622 for (var i = 0, l = changes.length || 0; i < l; ++i) {
623 var change = changes[i], intact2 = [], diff = change.diff || 0;
624 for (var j = 0, l2 = intact.length; j < l2; ++j) {
625 var range = intact[j];
626 if (change.to <= range.from)
627 intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart});
628 else if (range.to <= change.from)
629 intact2.push(range);
630 else {
631 if (change.from > range.from)
632 intact2.push({from: range.from, to: change.from, domStart: range.domStart})
633 if (change.to < range.to)
634 intact2.push({from: change.to + diff, to: range.to + diff,
635 domStart: range.domStart + (change.to - range.from)});
636 }
637 }
638 intact = intact2;
639 }
640
641 // Then, determine which lines we'd want to see, and which
642 // updates have to be made to get there.
643 var visible = visibleLines();
644 var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)),
645 to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)),
646 updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0;
647
648 for (var i = 0, l = intact.length; i < l; ++i) {
649 var range = intact[i];
650 if (range.to <= from) continue;
651 if (range.from >= to) break;
652 if (range.domStart > domPos || range.from > pos) {
653 updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos});
654 changedLines += range.from - pos;
655 }
656 pos = range.to;
657 domPos = range.domStart + (range.to - range.from);
658 }
659 if (domPos != domEnd || pos != to) {
660 changedLines += Math.abs(to - pos);
661 updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
662 }
663
664 if (!updates.length) return;
665 lineDiv.style.display = "none";
666 // If more than 30% of the screen needs update, just do a full
667 // redraw (which is quicker than patching)
668 if (changedLines > (visible.to - visible.from) * .3)
669 refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length));
670 // Otherwise, only update the stuff that needs updating.
671 else
672 patchDisplay(updates);
673 lineDiv.style.display = "";
674
675 // Position the mover div to align with the lines it's supposed
676 // to be showing (which will cover the visible display)
677 var different = from != showingFrom || to != showingTo || lastHeight != wrapper.clientHeight;
678 showingFrom = from; showingTo = to;
679 mover.style.top = (from * lineHeight()) + "px";
680 if (different) {
681 lastHeight = wrapper.clientHeight;
682 code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
683 updateGutter();
684 }
685
686 // Since this is all rather error prone, it is honoured with the
687 // only assertion in the whole file.
688 if (lineDiv.childNodes.length != showingTo - showingFrom)
689 throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) +
690 " nodes=" + lineDiv.childNodes.length);
691 updateCursor();
692 }
693
694 function refreshDisplay(from, to) {
695 var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start);
696 for (var i = from; i < to; ++i) {
697 var ch1 = null, ch2 = null;
698 if (inSel) {
699 ch1 = 0;
700 if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;}
701 }
702 else if (sel.from.line == i) {
703 if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
704 else {inSel = true; ch1 = sel.from.ch;}
705 }
706 html.push(lines[i].getHTML(ch1, ch2, true));
707 }
708 lineDiv.innerHTML = html.join("");
709 }
710 function patchDisplay(updates) {
711 // Slightly different algorithm for IE (badInnerHTML), since
712 // there .innerHTML on PRE nodes is dumb, and discards
713 // whitespace.
714 var sfrom = sel.from.line, sto = sel.to.line, off = 0,
715 scratch = badInnerHTML && document.createElement("div");
716 for (var i = 0, e = updates.length; i < e; ++i) {
717 var rec = updates[i];
718 var extra = (rec.to - rec.from) - rec.domSize;
719 var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null;
720 if (badInnerHTML)
721 for (var j = Math.max(-extra, rec.domSize); j > 0; --j)
722 lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
723 else if (extra) {
724 for (var j = Math.max(0, extra); j > 0; --j)
725 lineDiv.insertBefore(document.createElement("pre"), nodeAfter);
726 for (var j = Math.max(0, -extra); j > 0; --j)
727 lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
728 }
729 var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from;
730 for (var j = rec.from; j < rec.to; ++j) {
731 var ch1 = null, ch2 = null;
732 if (inSel) {
733 ch1 = 0;
734 if (sto == j) {inSel = false; ch2 = sel.to.ch;}
735 }
736 else if (sfrom == j) {
737 if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
738 else {inSel = true; ch1 = sel.from.ch;}
739 }
740 if (badInnerHTML) {
741 scratch.innerHTML = lines[j].getHTML(ch1, ch2, true);
742 lineDiv.insertBefore(scratch.firstChild, nodeAfter);
743 }
744 else {
745 node.innerHTML = lines[j].getHTML(ch1, ch2, false);
746 node.className = lines[j].className || "";
747 node = node.nextSibling;
748 }
749 }
750 off += extra;
751 }
752 }
753
754 function updateGutter() {
755 if (!options.gutter && !options.lineNumbers) return;
756 var hText = mover.offsetHeight, hEditor = wrapper.clientHeight;
757 gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
758 var html = [];
759 for (var i = showingFrom; i < showingTo; ++i) {
760 var marker = lines[i].gutterMarker;
761 var text = options.lineNumbers ? i + options.firstLineNumber : null;
762 if (marker && marker.text)
763 text = marker.text.replace("%N%", text != null ? text : "");
764 else if (text == null)
765 text = "\u00a0";
766 html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text, "</pre>");
767 }
768 gutter.style.display = "none";
769 gutterText.innerHTML = html.join("");
770 var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
771 while (val.length + pad.length < minwidth) pad += "\u00a0";
772 if (pad) firstNode.insertBefore(document.createTextNode(pad), firstNode.firstChild);
773 gutter.style.display = "";
774 lineSpace.style.marginLeft = gutter.offsetWidth + "px";
775 }
776 function updateCursor() {
777 var head = sel.inverted ? sel.from : sel.to;
778 var x = charX(head.line, head.ch) + "px", y = (head.line - showingFrom) * lineHeight() + "px";
779 inputDiv.style.top = y; inputDiv.style.left = x;
780 if (posEq(sel.from, sel.to)) {
781 cursor.style.top = y; cursor.style.left = x;
782 cursor.style.display = "";
783 }
784 else cursor.style.display = "none";
785 }
786
787 // Update the selection. Last two args are only used by
788 // updateLines, since they have to be expressed in the line
789 // numbers before the update.
790 function setSelection(from, to, oldFrom, oldTo) {
791 if (posEq(sel.from, from) && posEq(sel.to, to)) return;
792 var sh = shiftSelecting && clipPos(shiftSelecting);
793 if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
794 if (sh) {
795 if (posLess(sh, from)) from = sh;
796 else if (posLess(to, sh)) to = sh;
797 }
798
799 var startEq = posEq(sel.to, to), endEq = posEq(sel.from, from);
800 if (posEq(from, to)) sel.inverted = false;
801 else if (startEq && !endEq) sel.inverted = true;
802 else if (endEq && !startEq) sel.inverted = false;
803
804 // Some ugly logic used to only mark the lines that actually did
805 // see a change in selection as changed, rather than the whole
806 // selected range.
807 if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
808 if (posEq(from, to)) {
809 if (!posEq(sel.from, sel.to))
810 changes.push({from: oldFrom, to: oldTo + 1});
811 }
812 else if (posEq(sel.from, sel.to)) {
813 changes.push({from: from.line, to: to.line + 1});
814 }
815 else {
816 if (!posEq(from, sel.from)) {
817 if (from.line < oldFrom)
818 changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1});
819 else
820 changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1});
821 }
822 if (!posEq(to, sel.to)) {
823 if (to.line < oldTo)
824 changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1});
825 else
826 changes.push({from: Math.max(from.line, oldTo), to: to.line + 1});
827 }
828 }
829 sel.from = from; sel.to = to;
830 selectionChanged = true;
831 }
832 function setCursor(line, ch) {
833 var pos = clipPos({line: line, ch: ch || 0});
834 setSelection(pos, pos);
835 }
836
837 function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
838 function clipPos(pos) {
839 if (pos.line < 0) return {line: 0, ch: 0};
840 if (pos.line >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length};
841 var ch = pos.ch, linelen = lines[pos.line].text.length;
842 if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
843 else if (ch < 0) return {line: pos.line, ch: 0};
844 else return pos;
845 }
846
847 function scrollPage(down) {
848 var linesPerPage = Math.floor(wrapper.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
849 setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch);
850 }
851 function scrollEnd(top) {
852 setCursor(top ? 0 : lines.length - 1);
853 }
854 function selectAll() {
855 var endLine = lines.length - 1;
856 setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length});
857 }
858 function selectWordAt(pos) {
859 var line = lines[pos.line].text;
860 var start = pos.ch, end = pos.ch;
861 while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
862 while (end < line.length - 1 && /\w/.test(line.charAt(end))) ++end;
863 setSelection({line: pos.line, ch: start}, {line: pos.line, ch: end});
864 }
865 function handleEnter() {
866 replaceSelection("\n", "end");
867 if (options.enterMode != "flat")
868 indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
869 }
870 function handleTab(shift) {
871 shiftSelecting = null;
872 switch (options.tabMode) {
873 case "default":
874 return false;
875 case "indent":
876 for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, "smart");
877 break;
878 case "classic":
879 if (posEq(sel.from, sel.to)) {
880 if (shift) indentLine(sel.from.line, "smart");
881 else replaceSelection("\t", "end");
882 break;
883 }
884 case "shift":
885 for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, shift ? "subtract" : "add");
886 break;
887 }
888 return true;
889 }
890
891 function indentLine(n, how) {
892 if (how == "smart") {
893 if (!mode.indent) how = "prev";
894 else var state = getStateBefore(n);
895 }
896
897 var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
898 if (how == "prev") {
899 if (n) indentation = lines[n-1].indentation();
900 else indentation = 0;
901 }
902 else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
903 else if (how == "add") indentation = curSpace + options.indentUnit;
904 else if (how == "subtract") indentation = curSpace - options.indentUnit;
905 indentation = Math.max(0, indentation);
906 var diff = indentation - curSpace;
907
908 if (!diff) {
909 if (sel.from.line != n && sel.to.line != n) return;
910 var indentString = curSpaceString;
911 }
912 else {
913 var indentString = "", pos = 0;
914 if (options.indentWithTabs)
915 for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
916 while (pos < indentation) {++pos; indentString += " ";}
917 }
918
919 replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
920 }
921
922 function loadMode() {
923 mode = CodeMirror.getMode(options, options.mode);
924 for (var i = 0, l = lines.length; i < l; ++i)
925 lines[i].stateAfter = null;
926 work = [0];
927 }
928 function gutterChanged() {
929 var visible = options.gutter || options.lineNumbers;
930 gutter.style.display = visible ? "" : "none";
931 if (visible) updateGutter();
932 else lineDiv.parentNode.style.marginLeft = 0;
933 }
934
935 function markText(from, to, className) {
936 from = clipPos(from); to = clipPos(to);
937 var accum = [];
938 function add(line, from, to, className) {
939 var line = lines[line], mark = line.addMark(from, to, className);
940 mark.line = line;
941 accum.push(mark);
942 }
943 if (from.line == to.line) add(from.line, from.ch, to.ch, className);
944 else {
945 add(from.line, from.ch, null, className);
946 for (var i = from.line + 1, e = to.line; i < e; ++i)
947 add(i, 0, null, className);
948 add(to.line, 0, to.ch, className);
949 }
950 changes.push({from: from.line, to: to.line + 1});
951 return function() {
952 var start, end;
953 for (var i = 0; i < accum.length; ++i) {
954 var mark = accum[i], found = indexOf(lines, mark.line);
955 mark.line.removeMark(mark);
956 if (found > -1) {
957 if (start == null) start = found;
958 end = found;
959 }
960 }
961 if (start != null) changes.push({from: start, to: end + 1});
962 };
963 }
964
965 function addGutterMarker(line, text, className) {
966 if (typeof line == "number") line = lines[clipLine(line)];
967 line.gutterMarker = {text: text, style: className};
968 updateGutter();
969 return line;
970 }
971 function removeGutterMarker(line) {
972 if (typeof line == "number") line = lines[clipLine(line)];
973 line.gutterMarker = null;
974 updateGutter();
975 }
976 function setLineClass(line, className) {
977 if (typeof line == "number") {
978 var no = line;
979 line = lines[clipLine(line)];
980 }
981 else {
982 var no = indexOf(lines, line);
983 if (no == -1) return null;
984 }
985 line.className = className;
986 changes.push({from: no, to: no + 1});
987 return line;
988 }
989
990 function lineInfo(line) {
991 if (typeof line == "number") {
992 var n = line;
993 line = lines[line];
994 if (!line) return null;
995 }
996 else {
997 var n = indexOf(lines, line);
998 if (n == -1) return null;
999 }
1000 var marker = line.gutterMarker;
1001 return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
1002 }
1003
1004 // These are used to go from pixel positions to character
1005 // positions, taking tabs into account.
1006 function charX(line, pos) {
1007 var text = lines[line].text, span = measure.firstChild;
1008 if (text.lastIndexOf("\t", pos) == -1) return pos * charWidth();
1009 var old = span.firstChild.nodeValue;
1010 try {
1011 span.firstChild.nodeValue = text.slice(0, pos);
1012 return span.offsetWidth;
1013 } finally {span.firstChild.nodeValue = old;}
1014 }
1015 function charFromX(line, x) {
1016 var text = lines[line].text, cw = charWidth();
1017 if (x <= 0) return 0;
1018 if (text.indexOf("\t") == -1) return Math.min(text.length, Math.round(x / cw));
1019 var mspan = measure.firstChild, mtext = mspan.firstChild, old = mtext.nodeValue;
1020 try {
1021 mtext.nodeValue = text;
1022 var from = 0, fromX = 0, to = text.length, toX = mspan.offsetWidth;
1023 if (x > toX) return to;
1024 for (;;) {
1025 if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
1026 var middle = Math.ceil((from + to) / 2);
1027 mtext.nodeValue = text.slice(0, middle);
1028 var curX = mspan.offsetWidth;
1029 if (curX > x) {to = middle; toX = curX;}
1030 else {from = middle; fromX = curX;}
1031 }
1032 } finally {mtext.nodeValue = old;}
1033 }
1034
1035 function localCoords(pos, inLineWrap) {
1036 var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0);
1037 return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh};
1038 }
1039 function pageCoords(pos) {
1040 var local = localCoords(pos, true), off = eltOffset(lineSpace);
1041 return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1042 }
1043
1044 function lineHeight() {
1045 var nlines = lineDiv.childNodes.length;
1046 if (nlines) return lineDiv.offsetHeight / nlines;
1047 else return measure.firstChild.offsetHeight || 1;
1048 }
1049 function charWidth() {return (measure.firstChild.offsetWidth || 320) / 40;}
1050 function paddingTop() {return lineSpace.offsetTop;}
1051 function paddingLeft() {return lineSpace.offsetLeft;}
1052
1053 function posFromMouse(e, liberal) {
1054 var off = eltOffset(lineSpace),
1055 x = e.pageX() - off.left,
1056 y = e.pageY() - off.top;
1057 if (!liberal && e.target() != lineSpace.parentNode && !(e.target() == wrapper && y > (lines.length * lineHeight())))
1058 for (var n = e.target(); n != lineDiv && n != cursor; n = n.parentNode)
1059 if (!n || n == wrapper) return null;
1060 var line = showingFrom + Math.floor(y / lineHeight());
1061 return clipPos({line: line, ch: charFromX(clipLine(line), x)});
1062 }
1063 function onContextMenu(e) {
1064 var pos = posFromMouse(e);
1065 if (!pos || window.opera) return; // Opera is difficult.
1066 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1067 setCursor(pos.line, pos.ch);
1068
1069 var oldCSS = input.style.cssText;
1070 input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.pageY() - 1) +
1071 "px; left: " + (e.pageX() - 1) + "px; z-index: 1000; background: white; " +
1072 "border-width: 0; outline: none; overflow: hidden;";
1073 var val = input.value = getSelection();
1074 input.focus();
1075 setSelRange(input, 0, val.length);
1076 if (gecko) e.stop();
1077 leaveInputAlone = true;
1078 setTimeout(function() {
1079 if (input.value != val) operation(replaceSelection)(input.value, "end");
1080 input.style.cssText = oldCSS;
1081 leaveInputAlone = false;
1082 prepareInput();
1083 slowPoll();
1084 }, 50);
1085 }
1086
1087 // Cursor-blinking
1088 function restartBlink() {
1089 clearInterval(blinker);
1090 var on = true;
1091 cursor.style.visibility = "";
1092 blinker = setInterval(function() {
1093 cursor.style.visibility = (on = !on) ? "" : "hidden";
1094 }, 650);
1095 }
1096
1097 var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1098 function matchBrackets(autoclear) {
1099 var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1;
1100 var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1101 if (!match) return;
1102 var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
1103 for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
1104 if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
1105
1106 var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
1107 function scan(line, from, to) {
1108 if (!line.text) return;
1109 var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
1110 for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
1111 var text = st[i];
1112 if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;}
1113 for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
1114 if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
1115 var match = matching[cur];
1116 if (match.charAt(1) == ">" == forward) stack.push(cur);
1117 else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
1118 else if (!stack.length) return {pos: pos, match: true};
1119 }
1120 }
1121 }
1122 }
1123 for (var i = head.line, e = forward ? Math.min(i + 50, lines.length) : Math.max(0, i - 50); i != e; i+=d) {
1124 var line = lines[i], first = i == head.line;
1125 var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1126 if (found) {
1127 var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1128 var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1129 two = markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1130 var clear = operation(function(){one(); two();});
1131 if (autoclear) setTimeout(clear, 800);
1132 else bracketHighlighted = clear;
1133 break;
1134 }
1135 }
1136 }
1137
1138 // Finds the line to start with when starting a parse. Tries to
1139 // find a line with a stateAfter, so that it can start with a
1140 // valid state. If that fails, it returns the line with the
1141 // smallest indentation, which tends to need the least context to
1142 // parse correctly.
1143 function findStartLine(n) {
1144 var minindent, minline;
1145 for (var search = n, lim = n - 40; search > lim; --search) {
1146 if (search == 0) return 0;
1147 var line = lines[search-1];
1148 if (line.stateAfter) return search;
1149 var indented = line.indentation();
1150 if (minline == null || minindent > indented) {
1151 minline = search;
1152 minindent = indented;
1153 }
1154 }
1155 return minline;
1156 }
1157 function getStateBefore(n) {
1158 var start = findStartLine(n), state = start && lines[start-1].stateAfter;
1159 if (!state) state = startState(mode);
1160 else state = copyState(mode, state);
1161 for (var i = start; i < n; ++i) {
1162 var line = lines[i];
1163 line.highlight(mode, state);
1164 line.stateAfter = copyState(mode, state);
1165 }
1166 if (!lines[n].stateAfter) work.push(n);
1167 return state;
1168 }
1169 function highlightWorker() {
1170 var end = +new Date + options.workTime;
1171 while (work.length) {
1172 if (!lines[showingFrom].stateAfter) var task = showingFrom;
1173 else var task = work.pop();
1174 if (task >= lines.length) continue;
1175 var start = findStartLine(task), state = start && lines[start-1].stateAfter;
1176 if (state) state = copyState(mode, state);
1177 else state = startState(mode);
1178
1179 for (var i = start, l = lines.length; i < l; ++i) {
1180 var line = lines[i], hadState = line.stateAfter;
1181 if (+new Date > end) {
1182 work.push(i);
1183 startWorker(options.workDelay);
1184 changes.push({from: task, to: i});
1185 return;
1186 }
1187 var changed = line.highlight(mode, state);
1188 line.stateAfter = copyState(mode, state);
1189 if (hadState && !changed && line.text) break;
1190 }
1191 changes.push({from: task, to: i});
1192 }
1193 }
1194 function startWorker(time) {
1195 if (!work.length) return;
1196 highlight.set(time, operation(highlightWorker));
1197 }
1198
1199 // Operations are used to wrap changes in such a way that each
1200 // change won't have to update the cursor and display (which would
1201 // be awkward, slow, and error-prone), but instead updates are
1202 // batched and then all combined and executed at once.
1203 function startOperation() {
1204 updateInput = null; changes = []; textChanged = selectionChanged = false;
1205 }
1206 function endOperation() {
1207 var reScroll = false;
1208 if (selectionChanged) reScroll = !scrollCursorIntoView();
1209 if (changes.length) updateDisplay(changes);
1210 else if (selectionChanged) updateCursor();
1211 if (reScroll) scrollCursorIntoView();
1212 if (selectionChanged) restartBlink();
1213
1214 // updateInput can be set to a boolean value to force/prevent an
1215 // update.
1216 if (!leaveInputAlone && (updateInput === true || (updateInput !== false && selectionChanged)))
1217 prepareInput();
1218
1219 if (selectionChanged && options.onCursorActivity)
1220 options.onCursorActivity(instance);
1221 if (textChanged && options.onChange)
1222 options.onChange(instance);
1223 if (selectionChanged && options.matchBrackets)
1224 setTimeout(operation(function() {
1225 if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
1226 matchBrackets(false);
1227 }), 20);
1228 }
1229 var nestedOperation = 0;
1230 function operation(f) {
1231 return function() {
1232 if (!nestedOperation++) startOperation();
1233 try {var result = f.apply(this, arguments);}
1234 finally {if (!--nestedOperation) endOperation();}
1235 return result;
1236 };
1237 }
1238
1239 function SearchCursor(query, pos, caseFold) {
1240 this.atOccurrence = false;
1241 if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase();
1242
1243 if (pos && typeof pos == "object") pos = clipPos(pos);
1244 else pos = {line: 0, ch: 0};
1245 this.pos = {from: pos, to: pos};
1246
1247 // The matches method is filled in based on the type of query.
1248 // It takes a position and a direction, and returns an object
1249 // describing the next occurrence of the query, or null if no
1250 // more matches were found.
1251 if (typeof query != "string") // Regexp match
1252 this.matches = function(reverse, pos) {
1253 if (reverse) {
1254 var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0;
1255 while (match) {
1256 var ind = line.indexOf(match[0]);
1257 start += ind;
1258 line = line.slice(ind + 1);
1259 var newmatch = line.match(query);
1260 if (newmatch) match = newmatch;
1261 else break;
1262 }
1263 }
1264 else {
1265 var line = lines[pos.line].text.slice(pos.ch), match = line.match(query),
1266 start = match && pos.ch + line.indexOf(match[0]);
1267 }
1268 if (match)
1269 return {from: {line: pos.line, ch: start},
1270 to: {line: pos.line, ch: start + match[0].length},
1271 match: match};
1272 };
1273 else { // String query
1274 if (caseFold) query = query.toLowerCase();
1275 var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
1276 var target = query.split("\n");
1277 // Different methods for single-line and multi-line queries
1278 if (target.length == 1)
1279 this.matches = function(reverse, pos) {
1280 var line = fold(lines[pos.line].text), len = query.length, match;
1281 if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
1282 : (match = line.indexOf(query, pos.ch)) != -1)
1283 return {from: {line: pos.line, ch: match},
1284 to: {line: pos.line, ch: match + len}};
1285 };
1286 else
1287 this.matches = function(reverse, pos) {
1288 var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text);
1289 var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
1290 if (reverse ? offsetA >= pos.ch || offsetA != match.length
1291 : offsetA <= pos.ch || offsetA != line.length - match.length)
1292 return;
1293 for (;;) {
1294 if (reverse ? !ln : ln == lines.length - 1) return;
1295 line = fold(lines[ln += reverse ? -1 : 1].text);
1296 match = target[reverse ? --idx : ++idx];
1297 if (idx > 0 && idx < target.length - 1) {
1298 if (line != match) return;
1299 else continue;
1300 }
1301 var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
1302 if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
1303 return;
1304 var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB};
1305 return {from: reverse ? end : start, to: reverse ? start : end};
1306 }
1307 };
1308 }
1309 }
1310
1311 SearchCursor.prototype = {
1312 findNext: function() {return this.find(false);},
1313 findPrevious: function() {return this.find(true);},
1314
1315 find: function(reverse) {
1316 var self = this, pos = clipPos(reverse ? this.pos.from : this.pos.to);
1317 function savePosAndFail(line) {
1318 var pos = {line: line, ch: 0};
1319 self.pos = {from: pos, to: pos};
1320 self.atOccurrence = false;
1321 return false;
1322 }
1323
1324 for (;;) {
1325 if (this.pos = this.matches(reverse, pos)) {
1326 this.atOccurrence = true;
1327 return this.pos.match || true;
1328 }
1329 if (reverse) {
1330 if (!pos.line) return savePosAndFail(0);
1331 pos = {line: pos.line-1, ch: lines[pos.line-1].text.length};
1332 }
1333 else {
1334 if (pos.line == lines.length - 1) return savePosAndFail(lines.length);
1335 pos = {line: pos.line+1, ch: 0};
1336 }
1337 }
1338 },
1339
1340 from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
1341 to: function() {if (this.atOccurrence) return copyPos(this.pos.to);}
1342 };
1343
1344 return instance;
1345 } // (end of function CodeMirror)
1346
1347 // The default configuration options.
1348 CodeMirror.defaults = {
1349 value: "",
1350 mode: null,
1351 indentUnit: 2,
1352 indentWithTabs: false,
1353 tabMode: "classic",
1354 enterMode: "indent",
1355 electricChars: true,
1356 onKeyEvent: null,
1357 lineNumbers: false,
1358 gutter: false,
1359 firstLineNumber: 1,
1360 readOnly: false,
1361 onChange: null,
1362 onCursorActivity: null,
1363 onGutterClick: null,
1364 onFocus: null, onBlur: null, onScroll: null,
1365 matchBrackets: false,
1366 workTime: 100,
1367 workDelay: 200,
1368 undoDepth: 40,
1369 tabindex: null
1370 };
1371
1372 // Known modes, by name and by MIME
1373 var modes = {}, mimeModes = {};
1374 CodeMirror.defineMode = function(name, mode) {
1375 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
1376 modes[name] = mode;
1377 };
1378 CodeMirror.defineMIME = function(mime, spec) {
1379 mimeModes[mime] = spec;
1380 };
1381 CodeMirror.getMode = function(options, spec) {
1382 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
1383 spec = mimeModes[spec];
1384 if (typeof spec == "string")
1385 var mname = spec, config = {};
1386 else
1387 var mname = spec.name, config = spec;
1388 var mfactory = modes[mname];
1389 if (!mfactory) {
1390 if (window.console) console.warn("No mode " + mname + " found, falling back to plain text.");
1391 return CodeMirror.getMode(options, "text/plain");
1392 }
1393 return mfactory(options, config);
1394 }
1395 CodeMirror.listModes = function() {
1396 var list = [];
1397 for (var m in modes)
1398 if (modes.propertyIsEnumerable(m)) list.push(m);
1399 return list;
1400 };
1401 CodeMirror.listMIMEs = function() {
1402 var list = [];
1403 for (var m in mimeModes)
1404 if (mimeModes.propertyIsEnumerable(m)) list.push(m);
1405 return list;
1406 };
1407
1408 CodeMirror.fromTextArea = function(textarea, options) {
1409 if (!options) options = {};
1410 options.value = textarea.value;
1411 if (!options.tabindex && textarea.tabindex)
1412 options.tabindex = textarea.tabindex;
1413
1414 function save() {textarea.value = instance.getValue();}
1415 if (textarea.form) {
1416 // Deplorable hack to make the submit method do the right thing.
1417 var rmSubmit = connect(textarea.form, "submit", save, true);
1418 if (typeof textarea.form.submit == "function") {
1419 var realSubmit = textarea.form.submit;
1420 function wrappedSubmit() {
1421 save();
1422 textarea.form.submit = realSubmit;
1423 textarea.form.submit();
1424 textarea.form.submit = wrappedSubmit;
1425 }
1426 textarea.form.submit = wrappedSubmit;
1427 }
1428 }
1429
1430 textarea.style.display = "none";
1431 var instance = CodeMirror(function(node) {
1432 textarea.parentNode.insertBefore(node, textarea.nextSibling);
1433 }, options);
1434 instance.save = save;
1435 instance.toTextArea = function() {
1436 save();
1437 textarea.parentNode.removeChild(instance.getWrapperElement());
1438 textarea.style.display = "";
1439 if (textarea.form) {
1440 rmSubmit();
1441 if (typeof textarea.form.submit == "function")
1442 textarea.form.submit = realSubmit;
1443 }
1444 };
1445 return instance;
1446 };
1447
1448 // Utility functions for working with state. Exported because modes
1449 // sometimes need to do this.
1450 function copyState(mode, state) {
1451 if (state === true) return state;
1452 if (mode.copyState) return mode.copyState(state);
1453 var nstate = {};
1454 for (var n in state) {
1455 var val = state[n];
1456 if (val instanceof Array) val = val.concat([]);
1457 nstate[n] = val;
1458 }
1459 return nstate;
1460 }
1461 CodeMirror.startState = startState;
1462 function startState(mode, a1, a2) {
1463 return mode.startState ? mode.startState(a1, a2) : true;
1464 }
1465 CodeMirror.copyState = copyState;
1466
1467 // The character stream used by a mode's parser.
1468 function StringStream(string) {
1469 this.pos = this.start = 0;
1470 this.string = string;
1471 }
1472 StringStream.prototype = {
1473 eol: function() {return this.pos >= this.string.length;},
1474 sol: function() {return this.pos == 0;},
1475 peek: function() {return this.string.charAt(this.pos);},
1476 next: function() {
1477 if (this.pos < this.string.length)
1478 return this.string.charAt(this.pos++);
1479 },
1480 eat: function(match) {
1481 var ch = this.string.charAt(this.pos);
1482 if (typeof match == "string") var ok = ch == match;
1483 else var ok = ch && (match.test ? match.test(ch) : match(ch));
1484 if (ok) {++this.pos; return ch;}
1485 },
1486 eatWhile: function(match) {
1487 var start = this.start;
1488 while (this.eat(match)){}
1489 return this.pos > start;
1490 },
1491 eatSpace: function() {
1492 var start = this.pos;
1493 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
1494 return this.pos > start;
1495 },
1496 skipToEnd: function() {this.pos = this.string.length;},
1497 skipTo: function(ch) {
1498 var found = this.string.indexOf(ch, this.pos);
1499 if (found > -1) {this.pos = found; return true;}
1500 },
1501 backUp: function(n) {this.pos -= n;},
1502 column: function() {return countColumn(this.string, this.start);},
1503 indentation: function() {return countColumn(this.string);},
1504 match: function(pattern, consume, caseInsensitive) {
1505 if (typeof pattern == "string") {
1506 function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
1507 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
1508 if (consume !== false) this.pos += pattern.length;
1509 return true;
1510 }
1511 }
1512 else {
1513 var match = this.string.slice(this.pos).match(pattern);
1514 if (match && consume !== false) this.pos += match[0].length;
1515 return match;
1516 }
1517 },
1518 current: function(){return this.string.slice(this.start, this.pos);}
1519 };
1520
1521 // Line objects. These hold state related to a line, including
1522 // highlighting info (the styles array).
1523 function Line(text, styles) {
1524 this.styles = styles || [text, null];
1525 this.stateAfter = null;
1526 this.text = text;
1527 this.marked = this.gutterMarker = this.className = null;
1528 }
1529 Line.prototype = {
1530 // Replace a piece of a line, keeping the styles around it intact.
1531 replace: function(from, to, text) {
1532 var st = [], mk = this.marked;
1533 copyStyles(0, from, this.styles, st);
1534 if (text) st.push(text, null);
1535 copyStyles(to, this.text.length, this.styles, st);
1536 this.styles = st;
1537 this.text = this.text.slice(0, from) + text + this.text.slice(to);
1538 this.stateAfter = null;
1539 if (mk) {
1540 var diff = text.length - (to - from), end = this.text.length;
1541 function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;}
1542 for (var i = 0; i < mk.length; ++i) {
1543 var mark = mk[i], del = false;
1544 if (mark.from >= end) del = true;
1545 else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);}
1546 if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;}
1547 }
1548 }
1549 },
1550 // Split a line in two, again keeping styles intact.
1551 split: function(pos, textBefore) {
1552 var st = [textBefore, null];
1553 copyStyles(pos, this.text.length, this.styles, st);
1554 return new Line(textBefore + this.text.slice(pos), st);
1555 },
1556 addMark: function(from, to, style) {
1557 var mk = this.marked, mark = {from: from, to: to, style: style};
1558 if (this.marked == null) this.marked = [];
1559 this.marked.push(mark);
1560 this.marked.sort(function(a, b){return a.from - b.from;});
1561 return mark;
1562 },
1563 removeMark: function(mark) {
1564 var mk = this.marked;
1565 if (!mk) return;
1566 for (var i = 0; i < mk.length; ++i)
1567 if (mk[i] == mark) {mk.splice(i, 1); break;}
1568 },
1569 // Run the given mode's parser over a line, update the styles
1570 // array, which contains alternating fragments of text and CSS
1571 // classes.
1572 highlight: function(mode, state) {
1573 var stream = new StringStream(this.text), st = this.styles, pos = 0, changed = false;
1574 while (!stream.eol()) {
1575 var style = mode.token(stream, state);
1576 var substr = this.text.slice(stream.start, stream.pos);
1577 stream.start = stream.pos;
1578 if (pos && st[pos-1] == style)
1579 st[pos-2] += substr;
1580 else if (substr) {
1581 if (!changed && st[pos] != substr || st[pos+1] != style) changed = true;
1582 st[pos++] = substr; st[pos++] = style;
1583 }
1584 // Give up when line is ridiculously long
1585 if (stream.pos > 5000) {
1586 st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
1587 break;
1588 }
1589 }
1590 if (st.length != pos) {st.length = pos; changed = true;}
1591 return changed;
1592 },
1593 // Fetch the parser token for a given character. Useful for hacks
1594 // that want to inspect the mode state (say, for completion).
1595 getTokenAt: function(mode, state, ch) {
1596 var txt = this.text, stream = new StringStream(txt);
1597 while (stream.pos < ch && !stream.eol()) {
1598 stream.start = stream.pos;
1599 var style = mode.token(stream, state);
1600 }
1601 return {start: stream.start,
1602 end: stream.pos,
1603 string: stream.current(),
1604 className: style || null,
1605 state: state};
1606 },
1607 indentation: function() {return countColumn(this.text);},
1608 // Produces an HTML fragment for the line, taking selection,
1609 // marking, and highlighting into account.
1610 getHTML: function(sfrom, sto, includePre) {
1611 var html = [];
1612 if (includePre)
1613 html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
1614 function span(text, style) {
1615 if (!text) return;
1616 if (style) html.push('<span class="', style, '">', htmlEscape(text), "</span>");
1617 else html.push(htmlEscape(text));
1618 }
1619 var st = this.styles, allText = this.text, marked = this.marked;
1620 if (sfrom == sto) sfrom = null;
1621
1622 if (!allText)
1623 span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
1624 else if (!marked && sfrom == null)
1625 for (var i = 0, e = st.length; i < e; i+=2) span(st[i], st[i+1]);
1626 else {
1627 var pos = 0, i = 0, text = "", style, sg = 0;
1628 var markpos = -1, mark = null;
1629 function nextMark() {
1630 if (marked) {
1631 markpos += 1;
1632 mark = (markpos < marked.length) ? marked[markpos] : null;
1633 }
1634 }
1635 nextMark();
1636 while (pos < allText.length) {
1637 var upto = allText.length;
1638 var extraStyle = "";
1639 if (sfrom != null) {
1640 if (sfrom > pos) upto = sfrom;
1641 else if (sto == null || sto > pos) {
1642 extraStyle = " CodeMirror-selected";
1643 if (sto != null) upto = Math.min(upto, sto);
1644 }
1645 }
1646 while (mark && mark.to != null && mark.to <= pos) nextMark();
1647 if (mark) {
1648 if (mark.from > pos) upto = Math.min(upto, mark.from);
1649 else {
1650 extraStyle += " " + mark.style;
1651 if (mark.to != null) upto = Math.min(upto, mark.to);
1652 }
1653 }
1654 for (;;) {
1655 var end = pos + text.length;
1656 var apliedStyle = style;
1657 if (extraStyle) apliedStyle = style ? style + extraStyle : extraStyle;
1658 span(end > upto ? text.slice(0, upto - pos) : text, apliedStyle);
1659 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
1660 pos = end;
1661 text = st[i++]; style = st[i++];
1662 }
1663 }
1664 if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
1665 }
1666 if (includePre) html.push("</pre>");
1667 return html.join("");
1668 }
1669 };
1670 // Utility used by replace and split above
1671 function copyStyles(from, to, source, dest) {
1672 for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
1673 var part = source[i], end = pos + part.length;
1674 if (state == 0) {
1675 if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
1676 if (end >= from) state = 1;
1677 }
1678 else if (state == 1) {
1679 if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
1680 else dest.push(part, source[i+1]);
1681 }
1682 pos = end;
1683 }
1684 }
1685
1686 // The history object 'chunks' changes that are made close together
1687 // and at almost the same time into bigger undoable units.
1688 function History() {
1689 this.time = 0;
1690 this.done = []; this.undone = [];
1691 }
1692 History.prototype = {
1693 addChange: function(start, added, old) {
1694 this.undone.length = 0;
1695 var time = +new Date, last = this.done[this.done.length - 1];
1696 if (time - this.time > 400 || !last ||
1697 last.start > start + added || last.start + last.added < start - last.added + last.old.length)
1698 this.done.push({start: start, added: added, old: old});
1699 else {
1700 var oldoff = 0;
1701 if (start < last.start) {
1702 for (var i = last.start - start - 1; i >= 0; --i)
1703 last.old.unshift(old[i]);
1704 last.added += last.start - start;
1705 last.start = start;
1706 }
1707 else if (last.start < start) {
1708 oldoff = start - last.start;
1709 added += oldoff;
1710 }
1711 for (var i = last.added - oldoff, e = old.length; i < e; ++i)
1712 last.old.push(old[i]);
1713 if (last.added < added) last.added = added;
1714 }
1715 this.time = time;
1716 }
1717 };
1718
1719 // Event stopping compatibility wrapper.
1720 function stopEvent() {
1721 if (this.preventDefault) {this.preventDefault(); this.stopPropagation();}
1722 else {this.returnValue = false; this.cancelBubble = true;}
1723 }
1724 // Ensure an event has a stop method.
1725 function addStop(event) {
1726 if (!event.stop) event.stop = stopEvent;
1727 return event;
1728 }
1729
1730 // Event wrapper, exposing the few operations we need.
1731 function Event(orig) {this.e = orig;}
1732 Event.prototype = {
1733 stop: function() {stopEvent.call(this.e);},
1734 target: function() {return this.e.target || this.e.srcElement;},
1735 button: function() {
1736 if (this.e.which) return this.e.which;
1737 else if (this.e.button & 1) return 1;
1738 else if (this.e.button & 2) return 3;
1739 else if (this.e.button & 4) return 2;
1740 },
1741 pageX: function() {
1742 if (this.e.pageX != null) return this.e.pageX;
1743 else return this.e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
1744 },
1745 pageY: function() {
1746 if (this.e.pageY != null) return this.e.pageY;
1747 else return this.e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
1748 }
1749 };
1750
1751 // Event handler registration. If disconnect is true, it'll return a
1752 // function that unregisters the handler.
1753 function connect(node, type, handler, disconnect) {
1754 function wrapHandler(event) {handler(new Event(event || window.event));}
1755 if (typeof node.addEventListener == "function") {
1756 node.addEventListener(type, wrapHandler, false);
1757 if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
1758 }
1759 else {
1760 node.attachEvent("on" + type, wrapHandler);
1761 if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
1762 }
1763 }
1764
1765 function Delayed() {this.id = null;}
1766 Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
1767
1768 // Some IE versions don't preserve whitespace when setting the
1769 // innerHTML of a PRE tag.
1770 var badInnerHTML = (function() {
1771 var pre = document.createElement("pre");
1772 pre.innerHTML = " "; return !pre.innerHTML;
1773 })();
1774
1775 var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
1776
1777 var lineSep = "\n";
1778 // Feature-detect whether newlines in textareas are converted to \r\n
1779 (function () {
1780 var te = document.createElement("textarea");
1781 te.value = "foo\nbar";
1782 if (te.value.indexOf("\r") > -1) lineSep = "\r\n";
1783 }());
1784
1785 var tabSize = 8;
1786 var mac = /Mac/.test(navigator.platform);
1787 var movementKeys = {};
1788 for (var i = 35; i <= 40; ++i)
1789 movementKeys[i] = movementKeys["c" + i] = true;
1790
1791 // Counts the column offset in a string, taking tabs into account.
1792 // Used mostly to find indentation.
1793 function countColumn(string, end) {
1794 if (end == null) {
1795 end = string.search(/[^\s\u00a0]/);
1796 if (end == -1) end = string.length;
1797 }
1798 for (var i = 0, n = 0; i < end; ++i) {
1799 if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
1800 else ++n;
1801 }
1802 return n;
1803 }
1804
1805 // Find the position of an element by following the offsetParent chain.
1806 function eltOffset(node) {
1807 var x = 0, y = 0, n2 = node;
1808 for (var n = node; n; n = n.offsetParent) {x += n.offsetLeft; y += n.offsetTop;}
1809 for (var n = node; n != document.body; n = n.parentNode) {x -= n.scrollLeft; y -= n.scrollTop;}
1810 return {left: x, top: y};
1811 }
1812 // Get a node's text content.
1813 function eltText(node) {
1814 return node.textContent || node.innerText || node.nodeValue || "";
1815 }
1816
1817 // Operations on {line, ch} objects.
1818 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
1819 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
1820 function copyPos(x) {return {line: x.line, ch: x.ch};}
1821
1822 function htmlEscape(str) {
1823 return str.replace(/[<&]/g, function(str) {return str == "&" ? "&amp;" : "&lt;";});
1824 }
1825
1826 // Used to position the cursor after an undo/redo by finding the
1827 // last edited character.
1828 function editEnd(from, to) {
1829 if (!to) return from ? from.length : 0;
1830 if (!from) return to.length;
1831 for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
1832 if (from.charAt(i) != to.charAt(j)) break;
1833 return j + 1;
1834 }
1835
1836 function indexOf(collection, elt) {
1837 if (collection.indexOf) return collection.indexOf(elt);
1838 for (var i = 0, e = collection.length; i < e; ++i)
1839 if (collection[i] == elt) return i;
1840 return -1;
1841 }
1842
1843 // See if "".split is the broken IE version, if so, provide an
1844 // alternative way to split lines.
1845 if ("\n\nb".split(/\n/).length != 3)
1846 var splitLines = function(string) {
1847 var pos = 0, nl, result = [];
1848 while ((nl = string.indexOf("\n", pos)) > -1) {
1849 result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
1850 pos = nl + 1;
1851 }
1852 result.push(string.slice(pos));
1853 return result;
1854 };
1855 else
1856 var splitLines = function(string){return string.split(/\r?\n/);};
1857
1858 // Sane model of finding and setting the selection in a textarea
1859 if (window.getSelection) {
1860 var selRange = function(te) {
1861 try {return {start: te.selectionStart, end: te.selectionEnd};}
1862 catch(e) {return null;}
1863 };
1864 var setSelRange = function(te, start, end) {
1865 try {te.setSelectionRange(start, end);}
1866 catch(e) {} // Fails on Firefox when textarea isn't part of the document
1867 };
1868 }
1869 // IE model. Don't ask.
1870 else {
1871 var selRange = function(te) {
1872 try {var range = document.selection.createRange();}
1873 catch(e) {return null;}
1874 if (!range || range.parentElement() != te) return null;
1875 var val = te.value, len = val.length, localRange = te.createTextRange();
1876 localRange.moveToBookmark(range.getBookmark());
1877 var endRange = te.createTextRange();
1878 endRange.collapse(false);
1879
1880 if (localRange.compareEndPoints("StartToEnd", endRange) > -1)
1881 return {start: len, end: len};
1882
1883 var start = -localRange.moveStart("character", -len);
1884 for (var i = val.indexOf("\r"); i > -1 && i < start; i = val.indexOf("\r", i+1), start++) {}
1885
1886 if (localRange.compareEndPoints("EndToEnd", endRange) > -1)
1887 return {start: start, end: len};
1888
1889 var end = -localRange.moveEnd("character", -len);
1890 for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
1891 return {start: start, end: end};
1892 };
1893 var setSelRange = function(te, start, end) {
1894 var range = te.createTextRange();
1895 range.collapse(true);
1896 var endrange = range.duplicate();
1897 var newlines = 0, txt = te.value;
1898 for (var pos = txt.indexOf("\n"); pos > -1 && pos < start; pos = txt.indexOf("\n", pos + 1))
1899 ++newlines;
1900 range.move("character", start - newlines);
1901 for (; pos > -1 && pos < end; pos = txt.indexOf("\n", pos + 1))
1902 ++newlines;
1903 endrange.move("character", end - newlines);
1904 range.setEndPoint("EndToEnd", endrange);
1905 range.select();
1906 };
1907 }
1908
1909 CodeMirror.defineMode("null", function() {
1910 return {token: function(stream) {stream.skipToEnd();}};
1911 });
1912 CodeMirror.defineMIME("text/plain", "null");
1913
1914 return CodeMirror;
1915 })();
@@ -0,0 +1,66 b''
1 <%inherit file="/base/base.html"/>
2
3 <%def name="title()">
4 ${c.repo_name} ${_('Edit file')} - ${c.rhodecode_name}
5 </%def>
6
7 <%def name="js_extra()">
8 <script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script>
9 </%def>
10 <%def name="css_extra()">
11 <link rel="stylesheet" type="text/css" href="${h.url('/css/codemirror.css')}"/>
12 </%def>
13
14 <%def name="breadcrumbs_links()">
15 ${h.link_to(u'Home',h.url('/'))}
16 &raquo;
17 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
18 &raquo;
19 ${_('edit file')} @ R${c.cs.revision}:${h.short_id(c.cs.raw_id)}
20 </%def>
21
22 <%def name="page_nav()">
23 ${self.menu('files')}
24 </%def>
25 <%def name="main()">
26 <div class="box">
27 <!-- box / title -->
28 <div class="title">
29 ${self.breadcrumbs()}
30 <ul class="links">
31 <li>
32 <span style="text-transform: uppercase;">
33 <a href="#">${_('branch')}: ${c.cs.branch}</a></span>
34 </li>
35 </ul>
36 </div>
37 <div class="table">
38 <div id="files_data">
39 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
40 ${h.form(h.url.current(),method='post',id='eform')}
41 <div id="body" class="codeblock">
42 <pre id="editor_pre"></pre>
43 <textarea id="editor" name="content" style="display:none">${c.file.content|n}</textarea>
44
45 <div style="padding-top: 10px;">${_('commit message')}</div>
46 <textarea id="commit" name="message" style="height: 100px;width: 99%"></textarea>
47
48 </div>
49 <div style="text-align: right;padding-top: 5px">
50 <input id="reset" type="button" value="${_('Reset')}" class="ui-button-small" />
51 ${h.submit('commit',_('Commit changes'),class_="ui-button-small-blue")}
52 </div>
53 ${h.end_form()}
54 <script type="text/javascript">
55 var myCodeMirror = CodeMirror.fromTextArea(YUD.get('editor'),{
56 mode: "null",
57 lineNumbers:true
58 });
59 YUE.on('reset','click',function(){
60 window.location="${h.url('files_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.file.path)}";
61 })
62 </script>
63 </div>
64 </div>
65 </div>
66 </%def> No newline at end of file
@@ -1,348 +1,353 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10 from rhodecode.lib.utils import check_repo_fast as cr
11 11
12 12
13 13 def make_map(config):
14 14 """Create, configure and return the routes Mapper"""
15 15 rmap = Mapper(directory=config['pylons.paths']['controllers'],
16 16 always_scan=config['debug'])
17 17 rmap.minimization = False
18 18 rmap.explicit = False
19 19
20 20 def check_repo(environ, match_dict):
21 21 """
22 22 check for valid repository for proper 404 handling
23 23 :param environ:
24 24 :param match_dict:
25 25 """
26 26 repo_name = match_dict.get('repo_name')
27 27 return not cr(repo_name, config['base_path'])
28 28
29 29 # The ErrorController route (handles 404/500 error pages); it should
30 30 # likely stay at the top, ensuring it can always be resolved
31 31 rmap.connect('/error/{action}', controller='error')
32 32 rmap.connect('/error/{action}/{id}', controller='error')
33 33
34 34 #==========================================================================
35 35 # CUSTOM ROUTES HERE
36 36 #==========================================================================
37 37
38 38 #MAIN PAGE
39 39 rmap.connect('home', '/', controller='home', action='index')
40 40 rmap.connect('repo_switcher', '/repos', controller='home',
41 41 action='repo_switcher')
42 42 rmap.connect('bugtracker',
43 43 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
44 44 _static=True)
45 45 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
46 46
47 47 #ADMIN REPOSITORY REST ROUTES
48 48 with rmap.submapper(path_prefix='/_admin', controller='admin/repos') as m:
49 49 m.connect("repos", "/repos",
50 50 action="create", conditions=dict(method=["POST"]))
51 51 m.connect("repos", "/repos",
52 52 action="index", conditions=dict(method=["GET"]))
53 53 m.connect("formatted_repos", "/repos.{format}",
54 54 action="index",
55 55 conditions=dict(method=["GET"]))
56 56 m.connect("new_repo", "/repos/new",
57 57 action="new", conditions=dict(method=["GET"]))
58 58 m.connect("formatted_new_repo", "/repos/new.{format}",
59 59 action="new", conditions=dict(method=["GET"]))
60 60 m.connect("/repos/{repo_name:.*}",
61 61 action="update", conditions=dict(method=["PUT"],
62 62 function=check_repo))
63 63 m.connect("/repos/{repo_name:.*}",
64 64 action="delete", conditions=dict(method=["DELETE"],
65 65 function=check_repo))
66 66 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
67 67 action="edit", conditions=dict(method=["GET"],
68 68 function=check_repo))
69 69 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
70 70 action="edit", conditions=dict(method=["GET"],
71 71 function=check_repo))
72 72 m.connect("repo", "/repos/{repo_name:.*}",
73 73 action="show", conditions=dict(method=["GET"],
74 74 function=check_repo))
75 75 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
76 76 action="show", conditions=dict(method=["GET"],
77 77 function=check_repo))
78 78 #ajax delete repo perm user
79 79 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
80 80 action="delete_perm_user", conditions=dict(method=["DELETE"],
81 81 function=check_repo))
82 82 #ajax delete repo perm users_group
83 83 m.connect('delete_repo_users_group',
84 84 "/repos_delete_users_group/{repo_name:.*}",
85 85 action="delete_perm_users_group",
86 86 conditions=dict(method=["DELETE"], function=check_repo))
87 87
88 88 #settings actions
89 89 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
90 90 action="repo_stats", conditions=dict(method=["DELETE"],
91 91 function=check_repo))
92 92 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
93 93 action="repo_cache", conditions=dict(method=["DELETE"],
94 94 function=check_repo))
95 95 m.connect('repo_public_journal',
96 96 "/repos_public_journal/{repo_name:.*}",
97 97 action="repo_public_journal", conditions=dict(method=["PUT"],
98 98 function=check_repo))
99 99 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
100 100 action="repo_pull", conditions=dict(method=["PUT"],
101 101 function=check_repo))
102 102
103 103 #ADMIN REPOS GROUP REST ROUTES
104 104 rmap.resource('repos_group', 'repos_groups',
105 105 controller='admin/repos_groups', path_prefix='/_admin')
106 106
107 107 #ADMIN USER REST ROUTES
108 108 with rmap.submapper(path_prefix='/_admin', controller='admin/users') as m:
109 109 m.connect("users", "/users",
110 110 action="create", conditions=dict(method=["POST"]))
111 111 m.connect("users", "/users",
112 112 action="index", conditions=dict(method=["GET"]))
113 113 m.connect("formatted_users", "/users.{format}",
114 114 action="index", conditions=dict(method=["GET"]))
115 115 m.connect("new_user", "/users/new",
116 116 action="new", conditions=dict(method=["GET"]))
117 117 m.connect("formatted_new_user", "/users/new.{format}",
118 118 action="new", conditions=dict(method=["GET"]))
119 119 m.connect("update_user", "/users/{id}",
120 120 action="update", conditions=dict(method=["PUT"]))
121 121 m.connect("delete_user", "/users/{id}",
122 122 action="delete", conditions=dict(method=["DELETE"]))
123 123 m.connect("edit_user", "/users/{id}/edit",
124 124 action="edit", conditions=dict(method=["GET"]))
125 125 m.connect("formatted_edit_user",
126 126 "/users/{id}.{format}/edit",
127 127 action="edit", conditions=dict(method=["GET"]))
128 128 m.connect("user", "/users/{id}",
129 129 action="show", conditions=dict(method=["GET"]))
130 130 m.connect("formatted_user", "/users/{id}.{format}",
131 131 action="show", conditions=dict(method=["GET"]))
132 132
133 133 #EXTRAS USER ROUTES
134 134 m.connect("user_perm", "/users_perm/{id}",
135 135 action="update_perm", conditions=dict(method=["PUT"]))
136 136
137 137 #ADMIN USERS REST ROUTES
138 138 with rmap.submapper(path_prefix='/_admin',
139 139 controller='admin/users_groups') as m:
140 140 m.connect("users_groups", "/users_groups",
141 141 action="create", conditions=dict(method=["POST"]))
142 142 m.connect("users_groups", "/users_groups",
143 143 action="index", conditions=dict(method=["GET"]))
144 144 m.connect("formatted_users_groups", "/users_groups.{format}",
145 145 action="index", conditions=dict(method=["GET"]))
146 146 m.connect("new_users_group", "/users_groups/new",
147 147 action="new", conditions=dict(method=["GET"]))
148 148 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
149 149 action="new", conditions=dict(method=["GET"]))
150 150 m.connect("update_users_group", "/users_groups/{id}",
151 151 action="update", conditions=dict(method=["PUT"]))
152 152 m.connect("delete_users_group", "/users_groups/{id}",
153 153 action="delete", conditions=dict(method=["DELETE"]))
154 154 m.connect("edit_users_group", "/users_groups/{id}/edit",
155 155 action="edit", conditions=dict(method=["GET"]))
156 156 m.connect("formatted_edit_users_group",
157 157 "/users_groups/{id}.{format}/edit",
158 158 action="edit", conditions=dict(method=["GET"]))
159 159 m.connect("users_group", "/users_groups/{id}",
160 160 action="show", conditions=dict(method=["GET"]))
161 161 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
162 162 action="show", conditions=dict(method=["GET"]))
163 163
164 164 #EXTRAS USER ROUTES
165 165 m.connect("users_group_perm", "/users_groups_perm/{id}",
166 166 action="update_perm", conditions=dict(method=["PUT"]))
167 167
168 168 #ADMIN GROUP REST ROUTES
169 169 rmap.resource('group', 'groups',
170 170 controller='admin/groups', path_prefix='/_admin')
171 171
172 172 #ADMIN PERMISSIONS REST ROUTES
173 173 rmap.resource('permission', 'permissions',
174 174 controller='admin/permissions', path_prefix='/_admin')
175 175
176 176 ##ADMIN LDAP SETTINGS
177 177 rmap.connect('ldap_settings', '/_admin/ldap',
178 178 controller='admin/ldap_settings', action='ldap_settings',
179 179 conditions=dict(method=["POST"]))
180 180
181 181 rmap.connect('ldap_home', '/_admin/ldap',
182 182 controller='admin/ldap_settings')
183 183
184 184 #ADMIN SETTINGS REST ROUTES
185 185 with rmap.submapper(path_prefix='/_admin',
186 186 controller='admin/settings') as m:
187 187 m.connect("admin_settings", "/settings",
188 188 action="create", conditions=dict(method=["POST"]))
189 189 m.connect("admin_settings", "/settings",
190 190 action="index", conditions=dict(method=["GET"]))
191 191 m.connect("formatted_admin_settings", "/settings.{format}",
192 192 action="index", conditions=dict(method=["GET"]))
193 193 m.connect("admin_new_setting", "/settings/new",
194 194 action="new", conditions=dict(method=["GET"]))
195 195 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
196 196 action="new", conditions=dict(method=["GET"]))
197 197 m.connect("/settings/{setting_id}",
198 198 action="update", conditions=dict(method=["PUT"]))
199 199 m.connect("/settings/{setting_id}",
200 200 action="delete", conditions=dict(method=["DELETE"]))
201 201 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
202 202 action="edit", conditions=dict(method=["GET"]))
203 203 m.connect("formatted_admin_edit_setting",
204 204 "/settings/{setting_id}.{format}/edit",
205 205 action="edit", conditions=dict(method=["GET"]))
206 206 m.connect("admin_setting", "/settings/{setting_id}",
207 207 action="show", conditions=dict(method=["GET"]))
208 208 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
209 209 action="show", conditions=dict(method=["GET"]))
210 210 m.connect("admin_settings_my_account", "/my_account",
211 211 action="my_account", conditions=dict(method=["GET"]))
212 212 m.connect("admin_settings_my_account_update", "/my_account_update",
213 213 action="my_account_update", conditions=dict(method=["PUT"]))
214 214 m.connect("admin_settings_create_repository", "/create_repository",
215 215 action="create_repository", conditions=dict(method=["GET"]))
216 216
217 217 #ADMIN MAIN PAGES
218 218 with rmap.submapper(path_prefix='/_admin', controller='admin/admin') as m:
219 219 m.connect('admin_home', '', action='index')
220 220 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
221 221 action='add_repo')
222 222
223 223 #USER JOURNAL
224 224 rmap.connect('journal', '/_admin/journal', controller='journal')
225 225
226 226 rmap.connect('public_journal', '/_admin/public_journal',
227 227 controller='journal', action="public_journal")
228 228
229 229 rmap.connect('public_journal_rss', '/_admin/public_journal_rss',
230 230 controller='journal', action="public_journal_rss")
231 231
232 232 rmap.connect('public_journal_atom', '/_admin/public_journal_atom',
233 233 controller='journal', action="public_journal_atom")
234 234
235 235 rmap.connect('toggle_following', '/_admin/toggle_following',
236 236 controller='journal', action='toggle_following',
237 237 conditions=dict(method=["POST"]))
238 238
239 239 #SEARCH
240 240 rmap.connect('search', '/_admin/search', controller='search',)
241 241 rmap.connect('search_repo', '/_admin/search/{search_repo:.*}',
242 242 controller='search')
243 243
244 244 #LOGIN/LOGOUT/REGISTER/SIGN IN
245 245 rmap.connect('login_home', '/_admin/login', controller='login')
246 246 rmap.connect('logout_home', '/_admin/logout', controller='login',
247 247 action='logout')
248 248
249 249 rmap.connect('register', '/_admin/register', controller='login',
250 250 action='register')
251 251
252 252 rmap.connect('reset_password', '/_admin/password_reset',
253 253 controller='login', action='password_reset')
254 254
255 255 #FEEDS
256 256 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
257 257 controller='feed', action='rss',
258 258 conditions=dict(function=check_repo))
259 259
260 260 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
261 261 controller='feed', action='atom',
262 262 conditions=dict(function=check_repo))
263 263
264 264 #==========================================================================
265 265 # REPOSITORY ROUTES
266 266 #==========================================================================
267 267 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
268 268 controller='changeset', revision='tip',
269 269 conditions=dict(function=check_repo))
270 270
271 271 rmap.connect('raw_changeset_home',
272 272 '/{repo_name:.*}/raw-changeset/{revision}',
273 273 controller='changeset', action='raw_changeset',
274 274 revision='tip', conditions=dict(function=check_repo))
275 275
276 276 rmap.connect('summary_home', '/{repo_name:.*}',
277 277 controller='summary', conditions=dict(function=check_repo))
278 278
279 279 rmap.connect('summary_home', '/{repo_name:.*}/summary',
280 280 controller='summary', conditions=dict(function=check_repo))
281 281
282 282 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
283 283 controller='shortlog', conditions=dict(function=check_repo))
284 284
285 285 rmap.connect('branches_home', '/{repo_name:.*}/branches',
286 286 controller='branches', conditions=dict(function=check_repo))
287 287
288 288 rmap.connect('tags_home', '/{repo_name:.*}/tags',
289 289 controller='tags', conditions=dict(function=check_repo))
290 290
291 291 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
292 292 controller='changelog', conditions=dict(function=check_repo))
293 293
294 294 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
295 295 controller='files', revision='tip', f_path='',
296 296 conditions=dict(function=check_repo))
297 297
298 298 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
299 299 controller='files', action='diff', revision='tip', f_path='',
300 300 conditions=dict(function=check_repo))
301 301
302 302 rmap.connect('files_rawfile_home',
303 303 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
304 304 controller='files', action='rawfile', revision='tip',
305 305 f_path='', conditions=dict(function=check_repo))
306 306
307 307 rmap.connect('files_raw_home',
308 308 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
309 309 controller='files', action='raw', revision='tip', f_path='',
310 310 conditions=dict(function=check_repo))
311 311
312 312 rmap.connect('files_annotate_home',
313 313 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
314 314 controller='files', action='annotate', revision='tip',
315 315 f_path='', conditions=dict(function=check_repo))
316 316
317 rmap.connect('files_edit_home',
318 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
319 controller='files', action='edit', revision='tip',
320 f_path='', conditions=dict(function=check_repo))
321
317 322 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
318 323 controller='files', action='archivefile',
319 324 conditions=dict(function=check_repo))
320 325
321 326 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
322 327 controller='settings', action="delete",
323 328 conditions=dict(method=["DELETE"], function=check_repo))
324 329
325 330 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
326 331 controller='settings', action="update",
327 332 conditions=dict(method=["PUT"], function=check_repo))
328 333
329 334 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
330 335 controller='settings', action='index',
331 336 conditions=dict(function=check_repo))
332 337
333 338 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
334 339 controller='settings', action='fork_create',
335 340 conditions=dict(function=check_repo, method=["POST"]))
336 341
337 342 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
338 343 controller='settings', action='fork',
339 344 conditions=dict(function=check_repo))
340 345
341 346 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
342 347 controller='followers', action='followers',
343 348 conditions=dict(function=check_repo))
344 349
345 350 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
346 351 controller='forks', action='forks',
347 352 conditions=dict(function=check_repo))
348 353 return rmap
@@ -1,339 +1,396 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import mimetypes
29 import rhodecode.lib.helpers as h
29 import traceback
30 30
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.utils import EmptyChangeset
38 from rhodecode.model.repo import RepoModel
39
40 35 from vcs.backends import ARCHIVE_SPECS
41 36 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
42 37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
43 38 from vcs.nodes import FileNode, NodeKind
44 39 from vcs.utils import diffs as differ
45 40
41 from rhodecode.lib import convert_line_endings, detect_mode
42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.utils import EmptyChangeset
45 import rhodecode.lib.helpers as h
46 from rhodecode.model.repo import RepoModel
47
46 48 log = logging.getLogger(__name__)
47 49
48 50
49 51 class FilesController(BaseRepoController):
50 52
51 53 @LoginRequired()
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 'repository.admin')
54 54 def __before__(self):
55 55 super(FilesController, self).__before__()
56 56 c.cut_off_limit = self.cut_off_limit
57 57
58 58 def __get_cs_or_redirect(self, rev, repo_name):
59 59 """
60 60 Safe way to get changeset if error occur it redirects to tip with
61 61 proper message
62 62
63 63 :param rev: revision to fetch
64 64 :param repo_name: repo name to redirect after
65 65 """
66 66
67 67 try:
68 68 return c.rhodecode_repo.get_changeset(rev)
69 69 except EmptyRepositoryError, e:
70 70 h.flash(_('There are no files yet'), category='warning')
71 71 redirect(h.url('summary_home', repo_name=repo_name))
72 72
73 73 except RepositoryError, e:
74 74 h.flash(str(e), category='warning')
75 75 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
76 76
77 77 def __get_filenode_or_redirect(self, repo_name, cs, path):
78 78 """
79 79 Returns file_node, if error occurs or given path is directory,
80 80 it'll redirect to top level path
81 81
82 82 :param repo_name: repo_name
83 83 :param cs: given changeset
84 84 :param path: path to lookup
85 85 """
86 86
87 87 try:
88 88 file_node = cs.get_node(path)
89 89 if file_node.is_dir():
90 90 raise RepositoryError('given path is a directory')
91 91 except RepositoryError, e:
92 92 h.flash(str(e), category='warning')
93 93 redirect(h.url('files_home', repo_name=repo_name,
94 94 revision=cs.raw_id))
95 95
96 96 return file_node
97 97
98 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
99 'repository.admin')
98 100 def index(self, repo_name, revision, f_path):
99 101 #reditect to given revision from form if given
100 102 post_revision = request.POST.get('at_rev', None)
101 103 if post_revision:
102 104 cs = self.__get_cs_or_redirect(post_revision, repo_name)
103 105 redirect(url('files_home', repo_name=c.repo_name,
104 106 revision=cs.raw_id, f_path=f_path))
105 107
106 108 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
107 109 c.branch = request.GET.get('branch', None)
108 110 c.f_path = f_path
109 111
110 112 cur_rev = c.changeset.revision
111 113
112 114 #prev link
113 115 try:
114 116 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
115 117 c.url_prev = url('files_home', repo_name=c.repo_name,
116 118 revision=prev_rev.raw_id, f_path=f_path)
117 119 if c.branch:
118 120 c.url_prev += '?branch=%s' % c.branch
119 121 except (ChangesetDoesNotExistError, VCSError):
120 122 c.url_prev = '#'
121 123
122 124 #next link
123 125 try:
124 126 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
125 127 c.url_next = url('files_home', repo_name=c.repo_name,
126 128 revision=next_rev.raw_id, f_path=f_path)
127 129 if c.branch:
128 130 c.url_next += '?branch=%s' % c.branch
129 131 except (ChangesetDoesNotExistError, VCSError):
130 132 c.url_next = '#'
131 133
132 134 #files or dirs
133 135 try:
134 136 c.files_list = c.changeset.get_node(f_path)
135 137
136 138 if c.files_list.is_file():
137 139 c.file_history = self._get_node_history(c.changeset, f_path)
138 140 else:
139 141 c.file_history = []
140 142 except RepositoryError, e:
141 143 h.flash(str(e), category='warning')
142 144 redirect(h.url('files_home', repo_name=repo_name,
143 145 revision=revision))
144 146
145 147 return render('files/files.html')
146 148
149 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
150 'repository.admin')
147 151 def rawfile(self, repo_name, revision, f_path):
148 152 cs = self.__get_cs_or_redirect(revision, repo_name)
149 153 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
150 154
151 155 response.content_disposition = 'attachment; filename=%s' % \
152 156 f_path.split(os.sep)[-1].encode('utf8', 'replace')
153 157
154 158 response.content_type = file_node.mimetype
155 159 return file_node.content
156 160
161 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
162 'repository.admin')
157 163 def raw(self, repo_name, revision, f_path):
158 164 cs = self.__get_cs_or_redirect(revision, repo_name)
159 165 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
160 166
161 167 raw_mimetype_mapping = {
162 168 # map original mimetype to a mimetype used for "show as raw"
163 169 # you can also provide a content-disposition to override the
164 170 # default "attachment" disposition.
165 171 # orig_type: (new_type, new_dispo)
166 172
167 173 # show images inline:
168 174 'image/x-icon': ('image/x-icon', 'inline'),
169 175 'image/png': ('image/png', 'inline'),
170 176 'image/gif': ('image/gif', 'inline'),
171 177 'image/jpeg': ('image/jpeg', 'inline'),
172 178 'image/svg+xml': ('image/svg+xml', 'inline'),
173 179 }
174 180
175 181 mimetype = file_node.mimetype
176 182 try:
177 183 mimetype, dispo = raw_mimetype_mapping[mimetype]
178 184 except KeyError:
179 185 # we don't know anything special about this, handle it safely
180 186 if file_node.is_binary:
181 187 # do same as download raw for binary files
182 188 mimetype, dispo = 'application/octet-stream', 'attachment'
183 189 else:
184 190 # do not just use the original mimetype, but force text/plain,
185 191 # otherwise it would serve text/html and that might be unsafe.
186 192 # Note: underlying vcs library fakes text/plain mimetype if the
187 193 # mimetype can not be determined and it thinks it is not
188 194 # binary.This might lead to erroneous text display in some
189 195 # cases, but helps in other cases, like with text files
190 196 # without extension.
191 197 mimetype, dispo = 'text/plain', 'inline'
192 198
193 199 if dispo == 'attachment':
194 200 dispo = 'attachment; filename=%s' % \
195 201 f_path.split(os.sep)[-1].encode('utf8', 'replace')
196 202
197 203 response.content_disposition = dispo
198 204 response.content_type = mimetype
199 205 return file_node.content
200 206
207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
208 'repository.admin')
201 209 def annotate(self, repo_name, revision, f_path):
202 210 c.cs = self.__get_cs_or_redirect(revision, repo_name)
203 211 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
204 212
205 213 c.file_history = self._get_node_history(c.cs, f_path)
206 214 c.f_path = f_path
207 215 return render('files/files_annotate.html')
208 216
217 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
218 def edit(self, repo_name, revision, f_path):
219 r_post = request.POST
220
221 if c.rhodecode_repo.alias == 'hg':
222 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
223 elif c.rhodecode_repo.alias == 'git':
224 from vcs.backends.git import GitInMemoryChangeset as IMC
225
226 c.cs = self.__get_cs_or_redirect(revision, repo_name)
227 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
228
229 c.file_history = self._get_node_history(c.cs, f_path)
230 c.f_path = f_path
231
232 if r_post:
233
234 old_content = c.file.content
235 # modes: 0 - Unix, 1 - Mac, 2 - DOS
236 mode = detect_mode(old_content.splitlines(1)[0], 0)
237 content = convert_line_endings(r_post.get('content'), mode)
238 message = r_post.get('message') or (_('Edited %s via RhodeCode')
239 % (f_path))
240
241 if content == old_content:
242 h.flash(_('No changes'),
243 category='warning')
244 return redirect(url('changeset_home',
245 repo_name=c.repo_name, revision='tip'))
246 try:
247 new_node = FileNode(f_path, content)
248 m = IMC(c.rhodecode_repo)
249 m.change(new_node)
250 m.commit(message=message,
251 author=self.rhodecode_user.full_contact,
252 parents=[c.cs], branch=c.cs.branch)
253 h.flash(_('Successfully committed to %s' % f_path),
254 category='success')
255 except Exception, e:
256 log.error(traceback.format_exc())
257 h.flash(_('Error occurred during commit'), category='error')
258 return redirect(url('changeset_home',
259 repo_name=c.repo_name, revision='tip'))
260
261 return render('files/files_edit.html')
262
263 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
264 'repository.admin')
209 265 def archivefile(self, repo_name, fname):
210 266
211 267 fileformat = None
212 268 revision = None
213 269 ext = None
214 270
215 271 for a_type, ext_data in ARCHIVE_SPECS.items():
216 272 archive_spec = fname.split(ext_data[1])
217 273 if len(archive_spec) == 2 and archive_spec[1] == '':
218 274 fileformat = a_type or ext_data[1]
219 275 revision = archive_spec[0]
220 276 ext = ext_data[1]
221 277
222 278 try:
223 279 dbrepo = RepoModel().get_by_repo_name(repo_name)
224 280 if dbrepo.enable_downloads is False:
225 281 return _('downloads disabled')
226 282
227 283 cs = c.rhodecode_repo.get_changeset(revision)
228 284 content_type = ARCHIVE_SPECS[fileformat][0]
229 285 except ChangesetDoesNotExistError:
230 286 return _('Unknown revision %s') % revision
231 287 except EmptyRepositoryError:
232 288 return _('Empty repository')
233 289 except (ImproperArchiveTypeError, KeyError):
234 290 return _('Unknown archive type')
235 291
236 292 response.content_type = content_type
237 293 response.content_disposition = 'attachment; filename=%s-%s%s' \
238 294 % (repo_name, revision, ext)
239 295
240 296 return cs.get_chunked_archive(stream=None, kind=fileformat)
241 297
298 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
299 'repository.admin')
242 300 def diff(self, repo_name, f_path):
243 301 diff1 = request.GET.get('diff1')
244 302 diff2 = request.GET.get('diff2')
245 303 c.action = request.GET.get('diff')
246 304 c.no_changes = diff1 == diff2
247 305 c.f_path = f_path
248 306 c.big_diff = False
249 307
250 308 try:
251 309 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
252 310 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
253 311 node1 = c.changeset_1.get_node(f_path)
254 312 else:
255 313 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
256 314 node1 = FileNode('.', '', changeset=c.changeset_1)
257 315
258 316 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
259 317 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
260 318 node2 = c.changeset_2.get_node(f_path)
261 319 else:
262 320 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
263 321 node2 = FileNode('.', '', changeset=c.changeset_2)
264 322 except RepositoryError:
265 323 return redirect(url('files_home',
266 324 repo_name=c.repo_name, f_path=f_path))
267 325
268 326 if c.action == 'download':
269 327 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
270 328 format='gitdiff')
271 329
272 330 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
273 331 response.content_type = 'text/plain'
274 332 response.content_disposition = 'attachment; filename=%s' \
275 333 % diff_name
276 334 return diff.raw_diff()
277 335
278 336 elif c.action == 'raw':
279 337 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
280 338 format='gitdiff')
281 339 response.content_type = 'text/plain'
282 340 return diff.raw_diff()
283 341
284 342 elif c.action == 'diff':
285
286 343 if node1.is_binary or node2.is_binary:
287 344 c.cur_diff = _('Binary file')
288 345 elif node1.size > self.cut_off_limit or \
289 346 node2.size > self.cut_off_limit:
290 347 c.cur_diff = ''
291 348 c.big_diff = True
292 349 else:
293 350 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
294 351 format='gitdiff')
295 352 c.cur_diff = diff.as_html()
296 353 else:
297 354
298 355 #default option
299 356 if node1.is_binary or node2.is_binary:
300 357 c.cur_diff = _('Binary file')
301 358 elif node1.size > self.cut_off_limit or \
302 359 node2.size > self.cut_off_limit:
303 360 c.cur_diff = ''
304 361 c.big_diff = True
305 362
306 363 else:
307 364 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
308 365 format='gitdiff')
309 366 c.cur_diff = diff.as_html()
310 367
311 368 if not c.cur_diff and not c.big_diff:
312 369 c.no_changes = True
313 370 return render('files/file_diff.html')
314 371
315 372 def _get_node_history(self, cs, f_path):
316 373 changesets = cs.get_file_history(f_path)
317 374 hist_l = []
318 375
319 376 changesets_group = ([], _("Changesets"))
320 377 branches_group = ([], _("Branches"))
321 378 tags_group = ([], _("Tags"))
322 379
323 380 for chs in changesets:
324 381 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
325 382 changesets_group[0].append((chs.raw_id, n_desc,))
326 383
327 384 hist_l.append(changesets_group)
328 385
329 386 for name, chs in c.rhodecode_repo.branches.items():
330 387 #chs = chs.split(':')[-1]
331 388 branches_group[0].append((chs, name),)
332 389 hist_l.append(branches_group)
333 390
334 391 for name, chs in c.rhodecode_repo.tags.items():
335 392 #chs = chs.split(':')[-1]
336 393 tags_group[0].append((chs, name),)
337 394 hist_l.append(tags_group)
338 395
339 396 return hist_l
@@ -1,142 +1,166 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26
27 27
28 28 def __get_lem():
29 29 from pygments import lexers
30 30 from string import lower
31 31 from collections import defaultdict
32 32
33 33 d = defaultdict(lambda: [])
34 34
35 35 def __clean(s):
36 36 s = s.lstrip('*')
37 37 s = s.lstrip('.')
38 38
39 39 if s.find('[') != -1:
40 40 exts = []
41 41 start, stop = s.find('['), s.find(']')
42 42
43 43 for suffix in s[start + 1:stop]:
44 44 exts.append(s[:s.find('[')] + suffix)
45 45 return map(lower, exts)
46 46 else:
47 47 return map(lower, [s])
48 48
49 49 for lx, t in sorted(lexers.LEXERS.items()):
50 50 m = map(__clean, t[-2])
51 51 if m:
52 52 m = reduce(lambda x, y: x + y, m)
53 53 for ext in m:
54 54 desc = lx.replace('Lexer', '')
55 55 d[ext].append(desc)
56 56
57 57 return dict(d)
58 58
59 59 # language map is also used by whoosh indexer, which for those specified
60 60 # extensions will index it's content
61 61 LANGUAGES_EXTENSIONS_MAP = __get_lem()
62 62
63 63 #Additional mappings that are not present in the pygments lexers
64 64 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
65 65 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
66 66
67 67 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
68 68
69 69 def str2bool(_str):
70 70 """
71 71 returs True/False value from given string, it tries to translate the
72 72 string into boolean
73 73
74 74 :param _str: string value to translate into boolean
75 75 :rtype: boolean
76 76 :returns: boolean from given string
77 77 """
78 78 if _str is None:
79 79 return False
80 80 if _str in (True, False):
81 81 return _str
82 82 _str = str(_str).strip().lower()
83 83 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
84 84
85 def convert_line_endings(temp, mode):
86 from string import replace
87 #modes: 0 - Unix, 1 - Mac, 2 - DOS
88 if mode == 0:
89 temp = replace(temp, '\r\n', '\n')
90 temp = replace(temp, '\r', '\n')
91 elif mode == 1:
92 temp = replace(temp, '\r\n', '\r')
93 temp = replace(temp, '\n', '\r')
94 elif mode == 2:
95 import re
96 temp = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", temp)
97 return temp
98
99
100 def detect_mode(line, default):
101 if line.endswith('\r\n'):
102 return 2
103 elif line.endswith('\n'):
104 return 0
105 elif line.endswith('\r'):
106 return 1
107 else:
108 return default
85 109
86 110 def generate_api_key(username, salt=None):
87 111 """
88 112 Generates unique API key for given username,if salt is not given
89 113 it'll be generated from some random string
90 114
91 115 :param username: username as string
92 116 :param salt: salt to hash generate KEY
93 117 :rtype: str
94 118 :returns: sha1 hash from username+salt
95 119 """
96 120 from tempfile import _RandomNameSequence
97 121 import hashlib
98 122
99 123 if salt is None:
100 124 salt = _RandomNameSequence().next()
101 125
102 126 return hashlib.sha1(username + salt).hexdigest()
103 127
104 128
105 129 def safe_unicode(_str, from_encoding='utf8'):
106 130 """
107 131 safe unicode function. In case of UnicodeDecode error we try to return
108 132 unicode with errors replace
109 133
110 134 :param _str: string to decode
111 135 :rtype: unicode
112 136 :returns: unicode object
113 137 """
114 138
115 139 if isinstance(_str, unicode):
116 140 return _str
117 141
118 142 try:
119 143 u_str = unicode(_str, from_encoding)
120 144 except UnicodeDecodeError:
121 145 u_str = unicode(_str, from_encoding, 'replace')
122 146
123 147 return u_str
124 148
125 149
126 150 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
127 151 """
128 152 Custom engine_from_config functions that makes sure we use NullPool for
129 153 file based sqlite databases. This prevents errors on sqlite.
130 154
131 155 """
132 156 from sqlalchemy import engine_from_config as efc
133 157 from sqlalchemy.pool import NullPool
134 158
135 159 url = configuration[prefix + 'url']
136 160
137 161 if url.startswith('sqlite'):
138 162 kwargs.update({'poolclass':NullPool})
139 163
140 164 return efc(configuration, prefix, **kwargs)
141 165
142 166
@@ -1,601 +1,605 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :copyright: (c) 2010 by marcink.
10 10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 11 """
12 12 # This program is free software: you can redistribute it and/or modify
13 13 # it under the terms of the GNU General Public License as published by
14 14 # the Free Software Foundation, either version 3 of the License, or
15 15 # (at your option) any later version.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 24
25 25 import random
26 26 import logging
27 27 import traceback
28 28 import hashlib
29 29
30 30 from tempfile import _RandomNameSequence
31 31 from decorator import decorator
32 32
33 33 from pylons import config, session, url, request
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 38
39 39 if __platform__ in PLATFORM_WIN:
40 40 from hashlib import sha256
41 41 if __platform__ in PLATFORM_OTHERS:
42 42 import bcrypt
43 43
44 44 from rhodecode.lib import str2bool
45 45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 46 from rhodecode.lib.utils import get_repo_slug
47 47 from rhodecode.lib.auth_ldap import AuthLdap
48 48
49 49 from rhodecode.model import meta
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.db import Permission, RhodeCodeSettings
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class PasswordGenerator(object):
57 57 """This is a simple class for generating password from
58 58 different sets of characters
59 59 usage:
60 60 passwd_gen = PasswordGenerator()
61 61 #print 8-letter password containing only big and small letters
62 62 of alphabet
63 63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 64 """
65 65 ALPHABETS_NUM = r'''1234567890'''
66 66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75 75
76 76 def __init__(self, passwd=''):
77 77 self.passwd = passwd
78 78
79 79 def gen_password(self, len, type):
80 80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 81 return self.passwd
82 82
83 83
84 84 class RhodeCodeCrypto(object):
85 85
86 86 @classmethod
87 87 def hash_string(cls, str_):
88 88 """
89 89 Cryptographic function used for password hashing based on pybcrypt
90 90 or pycrypto in windows
91 91
92 92 :param password: password to hash
93 93 """
94 94 if __platform__ in PLATFORM_WIN:
95 95 return sha256(str_).hexdigest()
96 96 elif __platform__ in PLATFORM_OTHERS:
97 97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
98 98 else:
99 99 raise Exception('Unknown or unsupported platform %s' \
100 100 % __platform__)
101 101
102 102 @classmethod
103 103 def hash_check(cls, password, hashed):
104 104 """
105 105 Checks matching password with it's hashed value, runs different
106 106 implementation based on platform it runs on
107 107
108 108 :param password: password
109 109 :param hashed: password in hashed form
110 110 """
111 111
112 112 if __platform__ in PLATFORM_WIN:
113 113 return sha256(password).hexdigest() == hashed
114 114 elif __platform__ in PLATFORM_OTHERS:
115 115 return bcrypt.hashpw(password, hashed) == hashed
116 116 else:
117 117 raise Exception('Unknown or unsupported platform %s' \
118 118 % __platform__)
119 119
120 120
121 121 def get_crypt_password(password):
122 122 return RhodeCodeCrypto.hash_string(password)
123 123
124 124
125 125 def check_password(password, hashed):
126 126 return RhodeCodeCrypto.hash_check(password, hashed)
127 127
128 128
129 129 def generate_api_key(username, salt=None):
130 130 if salt is None:
131 131 salt = _RandomNameSequence().next()
132 132
133 133 return hashlib.sha1(username + salt).hexdigest()
134 134
135 135
136 136 def authfunc(environ, username, password):
137 137 """Dummy authentication function used in Mercurial/Git/ and access control,
138 138
139 139 :param environ: needed only for using in Basic auth
140 140 """
141 141 return authenticate(username, password)
142 142
143 143
144 144 def authenticate(username, password):
145 145 """Authentication function used for access control,
146 146 firstly checks for db authentication then if ldap is enabled for ldap
147 147 authentication, also creates ldap user if not in database
148 148
149 149 :param username: username
150 150 :param password: password
151 151 """
152 152
153 153 user_model = UserModel()
154 154 user = user_model.get_by_username(username, cache=False)
155 155
156 156 log.debug('Authenticating user using RhodeCode account')
157 157 if user is not None and not user.ldap_dn:
158 158 if user.active:
159 159 if user.username == 'default' and user.active:
160 160 log.info('user %s authenticated correctly as anonymous user',
161 161 username)
162 162 return True
163 163
164 164 elif user.username == username and check_password(password,
165 165 user.password):
166 166 log.info('user %s authenticated correctly', username)
167 167 return True
168 168 else:
169 169 log.warning('user %s is disabled', username)
170 170
171 171 else:
172 172 log.debug('Regular authentication failed')
173 173 user_obj = user_model.get_by_username(username, cache=False,
174 174 case_insensitive=True)
175 175
176 176 if user_obj is not None and not user_obj.ldap_dn:
177 177 log.debug('this user already exists as non ldap')
178 178 return False
179 179
180 180 ldap_settings = RhodeCodeSettings.get_ldap_settings()
181 181 #======================================================================
182 182 # FALLBACK TO LDAP AUTH IF ENABLE
183 183 #======================================================================
184 184 if str2bool(ldap_settings.get('ldap_active')):
185 185 log.debug("Authenticating user using ldap")
186 186 kwargs = {
187 187 'server': ldap_settings.get('ldap_host', ''),
188 188 'base_dn': ldap_settings.get('ldap_base_dn', ''),
189 189 'port': ldap_settings.get('ldap_port'),
190 190 'bind_dn': ldap_settings.get('ldap_dn_user'),
191 191 'bind_pass': ldap_settings.get('ldap_dn_pass'),
192 192 'tls_kind': ldap_settings.get('ldap_tls_kind'),
193 193 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
194 194 'ldap_filter': ldap_settings.get('ldap_filter'),
195 195 'search_scope': ldap_settings.get('ldap_search_scope'),
196 196 'attr_login': ldap_settings.get('ldap_attr_login'),
197 197 'ldap_version': 3,
198 198 }
199 199 log.debug('Checking for ldap authentication')
200 200 try:
201 201 aldap = AuthLdap(**kwargs)
202 202 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
203 203 password)
204 204 log.debug('Got ldap DN response %s', user_dn)
205 205
206 206 get_ldap_attr = lambda k:ldap_attrs.get(ldap_settings\
207 207 .get(k), [''])[0]
208 208
209 209 user_attrs = {
210 210 'name': get_ldap_attr('ldap_attr_firstname'),
211 211 'lastname': get_ldap_attr('ldap_attr_lastname'),
212 212 'email': get_ldap_attr('ldap_attr_email'),
213 213 }
214 214
215 215 if user_model.create_ldap(username, password, user_dn,
216 216 user_attrs):
217 217 log.info('created new ldap user %s', username)
218 218
219 219 return True
220 220 except (LdapUsernameError, LdapPasswordError,):
221 221 pass
222 222 except (Exception,):
223 223 log.error(traceback.format_exc())
224 224 pass
225 225 return False
226 226
227 227
228 228 class AuthUser(object):
229 229 """
230 230 A simple object that handles all attributes of user in RhodeCode
231 231
232 232 It does lookup based on API key,given user, or user present in session
233 233 Then it fills all required information for such user. It also checks if
234 234 anonymous access is enabled and if so, it returns default user as logged
235 235 in
236 236 """
237 237
238 238 def __init__(self, user_id=None, api_key=None):
239 239
240 240 self.user_id = user_id
241 241 self.api_key = None
242 242
243 243 self.username = 'None'
244 244 self.name = ''
245 245 self.lastname = ''
246 246 self.email = ''
247 247 self.is_authenticated = False
248 248 self.admin = False
249 249 self.permissions = {}
250 250 self._api_key = api_key
251 251 self.propagate_data()
252 252
253 253 def propagate_data(self):
254 254 user_model = UserModel()
255 255 self.anonymous_user = user_model.get_by_username('default', cache=True)
256 256 if self._api_key and self._api_key != self.anonymous_user.api_key:
257 257 #try go get user by api key
258 258 log.debug('Auth User lookup by API KEY %s', self._api_key)
259 259 user_model.fill_data(self, api_key=self._api_key)
260 260 else:
261 261 log.debug('Auth User lookup by USER ID %s', self.user_id)
262 262 if self.user_id is not None \
263 263 and self.user_id != self.anonymous_user.user_id:
264 264 user_model.fill_data(self, user_id=self.user_id)
265 265 else:
266 266 if self.anonymous_user.active is True:
267 267 user_model.fill_data(self,
268 268 user_id=self.anonymous_user.user_id)
269 269 #then we set this user is logged in
270 270 self.is_authenticated = True
271 271 else:
272 272 self.is_authenticated = False
273 273
274 274 log.debug('Auth User is now %s', self)
275 275 user_model.fill_perms(self)
276 276
277 277 @property
278 278 def is_admin(self):
279 279 return self.admin
280 280
281 @property
282 def full_contact(self):
283 return '%s %s <%s>' % (self.name, self.lastname, self.email)
284
281 285 def __repr__(self):
282 286 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
283 287 self.is_authenticated)
284 288
285 289 def set_authenticated(self, authenticated=True):
286 290
287 291 if self.user_id != self.anonymous_user.user_id:
288 292 self.is_authenticated = authenticated
289 293
290 294
291 295 def set_available_permissions(config):
292 296 """This function will propagate pylons globals with all available defined
293 297 permission given in db. We don't want to check each time from db for new
294 298 permissions since adding a new permission also requires application restart
295 299 ie. to decorate new views with the newly created permission
296 300
297 301 :param config: current pylons config instance
298 302
299 303 """
300 304 log.info('getting information about all available permissions')
301 305 try:
302 306 sa = meta.Session()
303 307 all_perms = sa.query(Permission).all()
304 308 except:
305 309 pass
306 310 finally:
307 311 meta.Session.remove()
308 312
309 313 config['available_permissions'] = [x.permission_name for x in all_perms]
310 314
311 315
312 316 #==============================================================================
313 317 # CHECK DECORATORS
314 318 #==============================================================================
315 319 class LoginRequired(object):
316 320 """
317 321 Must be logged in to execute this function else
318 322 redirect to login page
319 323
320 324 :param api_access: if enabled this checks only for valid auth token
321 325 and grants access based on valid token
322 326 """
323 327
324 328 def __init__(self, api_access=False):
325 329 self.api_access = api_access
326 330
327 331 def __call__(self, func):
328 332 return decorator(self.__wrapper, func)
329 333
330 334 def __wrapper(self, func, *fargs, **fkwargs):
331 335 cls = fargs[0]
332 336 user = cls.rhodecode_user
333 337
334 338 api_access_ok = False
335 339 if self.api_access:
336 340 log.debug('Checking API KEY access for %s', cls)
337 341 if user.api_key == request.GET.get('api_key'):
338 342 api_access_ok = True
339 343 else:
340 344 log.debug("API KEY token not valid")
341 345
342 346 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
343 347 if user.is_authenticated or api_access_ok:
344 348 log.debug('user %s is authenticated', user.username)
345 349 return func(*fargs, **fkwargs)
346 350 else:
347 351 log.warn('user %s NOT authenticated', user.username)
348 352 p = url.current()
349 353
350 354 log.debug('redirecting to login page with %s', p)
351 355 return redirect(url('login_home', came_from=p))
352 356
353 357
354 358 class NotAnonymous(object):
355 359 """Must be logged in to execute this function else
356 360 redirect to login page"""
357 361
358 362 def __call__(self, func):
359 363 return decorator(self.__wrapper, func)
360 364
361 365 def __wrapper(self, func, *fargs, **fkwargs):
362 366 cls = fargs[0]
363 367 self.user = cls.rhodecode_user
364 368
365 369 log.debug('Checking if user is not anonymous @%s', cls)
366 370
367 371 anonymous = self.user.username == 'default'
368 372
369 373 if anonymous:
370 374 p = ''
371 375 if request.environ.get('SCRIPT_NAME') != '/':
372 376 p += request.environ.get('SCRIPT_NAME')
373 377
374 378 p += request.environ.get('PATH_INFO')
375 379 if request.environ.get('QUERY_STRING'):
376 380 p += '?' + request.environ.get('QUERY_STRING')
377 381
378 382 import rhodecode.lib.helpers as h
379 383 h.flash(_('You need to be a registered user to '
380 384 'perform this action'),
381 385 category='warning')
382 386 return redirect(url('login_home', came_from=p))
383 387 else:
384 388 return func(*fargs, **fkwargs)
385 389
386 390
387 391 class PermsDecorator(object):
388 392 """Base class for controller decorators"""
389 393
390 394 def __init__(self, *required_perms):
391 395 available_perms = config['available_permissions']
392 396 for perm in required_perms:
393 397 if perm not in available_perms:
394 398 raise Exception("'%s' permission is not defined" % perm)
395 399 self.required_perms = set(required_perms)
396 400 self.user_perms = None
397 401
398 402 def __call__(self, func):
399 403 return decorator(self.__wrapper, func)
400 404
401 405 def __wrapper(self, func, *fargs, **fkwargs):
402 406 cls = fargs[0]
403 407 self.user = cls.rhodecode_user
404 408 self.user_perms = self.user.permissions
405 409 log.debug('checking %s permissions %s for %s %s',
406 410 self.__class__.__name__, self.required_perms, cls,
407 411 self.user)
408 412
409 413 if self.check_permissions():
410 414 log.debug('Permission granted for %s %s', cls, self.user)
411 415 return func(*fargs, **fkwargs)
412 416
413 417 else:
414 418 log.warning('Permission denied for %s %s', cls, self.user)
415 419 #redirect with forbidden ret code
416 420 return abort(403)
417 421
418 422 def check_permissions(self):
419 423 """Dummy function for overriding"""
420 424 raise Exception('You have to write this function in child class')
421 425
422 426
423 427 class HasPermissionAllDecorator(PermsDecorator):
424 428 """Checks for access permission for all given predicates. All of them
425 429 have to be meet in order to fulfill the request
426 430 """
427 431
428 432 def check_permissions(self):
429 433 if self.required_perms.issubset(self.user_perms.get('global')):
430 434 return True
431 435 return False
432 436
433 437
434 438 class HasPermissionAnyDecorator(PermsDecorator):
435 439 """Checks for access permission for any of given predicates. In order to
436 440 fulfill the request any of predicates must be meet
437 441 """
438 442
439 443 def check_permissions(self):
440 444 if self.required_perms.intersection(self.user_perms.get('global')):
441 445 return True
442 446 return False
443 447
444 448
445 449 class HasRepoPermissionAllDecorator(PermsDecorator):
446 450 """Checks for access permission for all given predicates for specific
447 451 repository. All of them have to be meet in order to fulfill the request
448 452 """
449 453
450 454 def check_permissions(self):
451 455 repo_name = get_repo_slug(request)
452 456 try:
453 457 user_perms = set([self.user_perms['repositories'][repo_name]])
454 458 except KeyError:
455 459 return False
456 460 if self.required_perms.issubset(user_perms):
457 461 return True
458 462 return False
459 463
460 464
461 465 class HasRepoPermissionAnyDecorator(PermsDecorator):
462 466 """Checks for access permission for any of given predicates for specific
463 467 repository. In order to fulfill the request any of predicates must be meet
464 468 """
465 469
466 470 def check_permissions(self):
467 471 repo_name = get_repo_slug(request)
468 472
469 473 try:
470 474 user_perms = set([self.user_perms['repositories'][repo_name]])
471 475 except KeyError:
472 476 return False
473 477 if self.required_perms.intersection(user_perms):
474 478 return True
475 479 return False
476 480
477 481
478 482 #==============================================================================
479 483 # CHECK FUNCTIONS
480 484 #==============================================================================
481 485 class PermsFunction(object):
482 486 """Base function for other check functions"""
483 487
484 488 def __init__(self, *perms):
485 489 available_perms = config['available_permissions']
486 490
487 491 for perm in perms:
488 492 if perm not in available_perms:
489 493 raise Exception("'%s' permission in not defined" % perm)
490 494 self.required_perms = set(perms)
491 495 self.user_perms = None
492 496 self.granted_for = ''
493 497 self.repo_name = None
494 498
495 499 def __call__(self, check_Location=''):
496 500 user = session.get('rhodecode_user', False)
497 501 if not user:
498 502 return False
499 503 self.user_perms = user.permissions
500 504 self.granted_for = user
501 505 log.debug('checking %s %s %s', self.__class__.__name__,
502 506 self.required_perms, user)
503 507
504 508 if self.check_permissions():
505 509 log.debug('Permission granted %s @ %s', self.granted_for,
506 510 check_Location or 'unspecified location')
507 511 return True
508 512
509 513 else:
510 514 log.warning('Permission denied for %s @ %s', self.granted_for,
511 515 check_Location or 'unspecified location')
512 516 return False
513 517
514 518 def check_permissions(self):
515 519 """Dummy function for overriding"""
516 520 raise Exception('You have to write this function in child class')
517 521
518 522
519 523 class HasPermissionAll(PermsFunction):
520 524 def check_permissions(self):
521 525 if self.required_perms.issubset(self.user_perms.get('global')):
522 526 return True
523 527 return False
524 528
525 529
526 530 class HasPermissionAny(PermsFunction):
527 531 def check_permissions(self):
528 532 if self.required_perms.intersection(self.user_perms.get('global')):
529 533 return True
530 534 return False
531 535
532 536
533 537 class HasRepoPermissionAll(PermsFunction):
534 538
535 539 def __call__(self, repo_name=None, check_Location=''):
536 540 self.repo_name = repo_name
537 541 return super(HasRepoPermissionAll, self).__call__(check_Location)
538 542
539 543 def check_permissions(self):
540 544 if not self.repo_name:
541 545 self.repo_name = get_repo_slug(request)
542 546
543 547 try:
544 548 self.user_perms = set([self.user_perms['reposit'
545 549 'ories'][self.repo_name]])
546 550 except KeyError:
547 551 return False
548 552 self.granted_for = self.repo_name
549 553 if self.required_perms.issubset(self.user_perms):
550 554 return True
551 555 return False
552 556
553 557
554 558 class HasRepoPermissionAny(PermsFunction):
555 559
556 560 def __call__(self, repo_name=None, check_Location=''):
557 561 self.repo_name = repo_name
558 562 return super(HasRepoPermissionAny, self).__call__(check_Location)
559 563
560 564 def check_permissions(self):
561 565 if not self.repo_name:
562 566 self.repo_name = get_repo_slug(request)
563 567
564 568 try:
565 569 self.user_perms = set([self.user_perms['reposi'
566 570 'tories'][self.repo_name]])
567 571 except KeyError:
568 572 return False
569 573 self.granted_for = self.repo_name
570 574 if self.required_perms.intersection(self.user_perms):
571 575 return True
572 576 return False
573 577
574 578
575 579 #==============================================================================
576 580 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
577 581 #==============================================================================
578 582 class HasPermissionAnyMiddleware(object):
579 583 def __init__(self, *perms):
580 584 self.required_perms = set(perms)
581 585
582 586 def __call__(self, user, repo_name):
583 587 usr = AuthUser(user.user_id)
584 588 try:
585 589 self.user_perms = set([usr.permissions['repositories'][repo_name]])
586 590 except:
587 591 self.user_perms = set()
588 592 self.granted_for = ''
589 593 self.username = user.username
590 594 self.repo_name = repo_name
591 595 return self.check_permissions()
592 596
593 597 def check_permissions(self):
594 598 log.debug('checking mercurial protocol '
595 599 'permissions %s for user:%s repository:%s', self.user_perms,
596 600 self.username, self.repo_name)
597 601 if self.required_perms.intersection(self.user_perms):
598 602 log.debug('permission granted')
599 603 return True
600 604 log.debug('permission denied')
601 605 return False
@@ -1,2567 +1,2576 b''
1 1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
2 2 border:0;
3 3 outline:0;
4 4 font-size:100%;
5 5 vertical-align:baseline;
6 6 background:transparent;
7 7 margin:0;
8 8 padding:0;
9 9 }
10 10
11 11 body {
12 12 line-height:1;
13 13 height:100%;
14 14 background:url("../images/background.png") repeat scroll 0 0 #B0B0B0;
15 15 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
16 16 font-size:12px;
17 17 color:#000;
18 18 margin:0;
19 19 padding:0;
20 20 }
21 21
22 22 ol,ul {
23 23 list-style:none;
24 24 }
25 25
26 26 blockquote,q {
27 27 quotes:none;
28 28 }
29 29
30 30 blockquote:before,blockquote:after,q:before,q:after {
31 31 content:none;
32 32 }
33 33
34 34 :focus {
35 35 outline:0;
36 36 }
37 37
38 38 del {
39 39 text-decoration:line-through;
40 40 }
41 41
42 42 table {
43 43 border-collapse:collapse;
44 44 border-spacing:0;
45 45 }
46 46
47 47 html {
48 48 height:100%;
49 49 }
50 50
51 51 a {
52 52 color:#003367;
53 53 text-decoration:none;
54 54 cursor:pointer;
55 55 font-weight:700;
56 56 }
57 57
58 58 a:hover {
59 59 color:#316293;
60 60 text-decoration:underline;
61 61 }
62 62
63 63 h1,h2,h3,h4,h5,h6 {
64 64 color:#292929;
65 65 font-weight:700;
66 66 }
67 67
68 68 h1 {
69 69 font-size:22px;
70 70 }
71 71
72 72 h2 {
73 73 font-size:20px;
74 74 }
75 75
76 76 h3 {
77 77 font-size:18px;
78 78 }
79 79
80 80 h4 {
81 81 font-size:16px;
82 82 }
83 83
84 84 h5 {
85 85 font-size:14px;
86 86 }
87 87
88 88 h6 {
89 89 font-size:11px;
90 90 }
91 91
92 92 ul.circle {
93 93 list-style-type:circle;
94 94 }
95 95
96 96 ul.disc {
97 97 list-style-type:disc;
98 98 }
99 99
100 100 ul.square {
101 101 list-style-type:square;
102 102 }
103 103
104 104 ol.lower-roman {
105 105 list-style-type:lower-roman;
106 106 }
107 107
108 108 ol.upper-roman {
109 109 list-style-type:upper-roman;
110 110 }
111 111
112 112 ol.lower-alpha {
113 113 list-style-type:lower-alpha;
114 114 }
115 115
116 116 ol.upper-alpha {
117 117 list-style-type:upper-alpha;
118 118 }
119 119
120 120 ol.decimal {
121 121 list-style-type:decimal;
122 122 }
123 123
124 124 div.color {
125 125 clear:both;
126 126 overflow:hidden;
127 127 position:absolute;
128 128 background:#FFF;
129 129 margin:7px 0 0 60px;
130 130 padding:1px 1px 1px 0;
131 131 }
132 132
133 133 div.color a {
134 134 width:15px;
135 135 height:15px;
136 136 display:block;
137 137 float:left;
138 138 margin:0 0 0 1px;
139 139 padding:0;
140 140 }
141 141
142 142 div.options {
143 143 clear:both;
144 144 overflow:hidden;
145 145 position:absolute;
146 146 background:#FFF;
147 147 margin:7px 0 0 162px;
148 148 padding:0;
149 149 }
150 150
151 151 div.options a {
152 152 height:1%;
153 153 display:block;
154 154 text-decoration:none;
155 155 margin:0;
156 156 padding:3px 8px;
157 157 }
158 158
159 159 .top-left-rounded-corner {
160 160 -webkit-border-top-left-radius: 8px;
161 161 -khtml-border-radius-topleft: 8px;
162 162 -moz-border-radius-topleft: 8px;
163 163 border-top-left-radius: 8px;
164 164 }
165 165
166 166 .top-right-rounded-corner {
167 167 -webkit-border-top-right-radius: 8px;
168 168 -khtml-border-radius-topright: 8px;
169 169 -moz-border-radius-topright: 8px;
170 170 border-top-right-radius: 8px;
171 171 }
172 172
173 173 .bottom-left-rounded-corner {
174 174 -webkit-border-bottom-left-radius: 8px;
175 175 -khtml-border-radius-bottomleft: 8px;
176 176 -moz-border-radius-bottomleft: 8px;
177 177 border-bottom-left-radius: 8px;
178 178 }
179 179
180 180 .bottom-right-rounded-corner {
181 181 -webkit-border-bottom-right-radius: 8px;
182 182 -khtml-border-radius-bottomright: 8px;
183 183 -moz-border-radius-bottomright: 8px;
184 184 border-bottom-right-radius: 8px;
185 185 }
186 186
187 187
188 188 #header {
189 189 margin:0;
190 190 padding:0 10px;
191 191 }
192 192
193 193
194 194 #header ul#logged-user{
195 195 margin-bottom:5px !important;
196 196 -webkit-border-radius: 0px 0px 8px 8px;
197 197 -khtml-border-radius: 0px 0px 8px 8px;
198 198 -moz-border-radius: 0px 0px 8px 8px;
199 199 border-radius: 0px 0px 8px 8px;
200 200 height:37px;
201 201 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367
202 202 }
203 203
204 204 #header ul#logged-user li {
205 205 list-style:none;
206 206 float:left;
207 207 margin:8px 0 0;
208 208 padding:4px 12px;
209 209 border-left: 1px solid #316293;
210 210 }
211 211
212 212 #header ul#logged-user li.first {
213 213 border-left:none;
214 214 margin:4px;
215 215 }
216 216
217 217 #header ul#logged-user li.first div.gravatar {
218 218 margin-top:-2px;
219 219 }
220 220
221 221 #header ul#logged-user li.first div.account {
222 222 padding-top:4px;
223 223 float:left;
224 224 }
225 225
226 226 #header ul#logged-user li.last {
227 227 border-right:none;
228 228 }
229 229
230 230 #header ul#logged-user li a {
231 231 color:#fff;
232 232 font-weight:700;
233 233 text-decoration:none;
234 234 }
235 235
236 236 #header ul#logged-user li a:hover {
237 237 text-decoration:underline;
238 238 }
239 239
240 240 #header ul#logged-user li.highlight a {
241 241 color:#fff;
242 242 }
243 243
244 244 #header ul#logged-user li.highlight a:hover {
245 245 color:#FFF;
246 246 }
247 247
248 248 #header #header-inner {
249 249 height:40px;
250 250 clear:both;
251 251 position:relative;
252 252 background:#003367 url("../images/header_inner.png") repeat-x;
253 253 border-bottom:2px solid #fff;
254 254 margin:0;
255 255 padding:0;
256 256 }
257 257
258 258 #header #header-inner #home a {
259 259 height:40px;
260 260 width:46px;
261 261 display:block;
262 262 background:url("../images/button_home.png");
263 263 background-position:0 0;
264 264 margin:0;
265 265 padding:0;
266 266 }
267 267
268 268 #header #header-inner #home a:hover {
269 269 background-position:0 -40px;
270 270 }
271 271
272 272 #header #header-inner #logo h1 {
273 273 color:#FFF;
274 274 font-size:18px;
275 275 margin:10px 0 0 13px;
276 276 padding:0;
277 277 }
278 278
279 279 #header #header-inner #logo a {
280 280 color:#fff;
281 281 text-decoration:none;
282 282 }
283 283
284 284 #header #header-inner #logo a:hover {
285 285 color:#bfe3ff;
286 286 }
287 287
288 288 #header #header-inner #quick,#header #header-inner #quick ul {
289 289 position:relative;
290 290 float:right;
291 291 list-style-type:none;
292 292 list-style-position:outside;
293 293 margin:10px 5px 0 0;
294 294 padding:0;
295 295 }
296 296
297 297 #header #header-inner #quick li {
298 298 position:relative;
299 299 float:left;
300 300 margin:0 5px 0 0;
301 301 padding:0;
302 302 }
303 303
304 304 #header #header-inner #quick li a {
305 305 top:0;
306 306 left:0;
307 307 height:1%;
308 308 display:block;
309 309 clear:both;
310 310 overflow:hidden;
311 311 color:#FFF;
312 312 font-weight:700;
313 313 text-decoration:none;
314 314 background:#369 url("../images/quick_l.png") no-repeat top left;
315 315 padding:0;
316 316 }
317 317
318 318 #header #header-inner #quick li span.short {
319 319 padding:9px 6px 8px 6px;
320 320 }
321 321
322 322 #header #header-inner #quick li span {
323 323 top:0;
324 324 right:0;
325 325 height:1%;
326 326 display:block;
327 327 float:left;
328 328 background:url("../images/quick_r.png") no-repeat top right;
329 329 border-left:1px solid #3f6f9f;
330 330 margin:0;
331 331 padding:10px 12px 8px 10px;
332 332 }
333 333
334 334 #header #header-inner #quick li span.normal {
335 335 border:none;
336 336 padding:10px 12px 8px;
337 337 }
338 338
339 339 #header #header-inner #quick li span.icon {
340 340 top:0;
341 341 left:0;
342 342 border-left:none;
343 343 background:url("../images/quick_l.png") no-repeat top left;
344 344 border-right:1px solid #2e5c89;
345 345 padding:8px 8px 4px;
346 346 }
347 347
348 348 #header #header-inner #quick li span.icon_short {
349 349 top:0;
350 350 left:0;
351 351 border-left:none;
352 352 background:url("../images/quick_l.png") no-repeat top left;
353 353 border-right:1px solid #2e5c89;
354 354 padding:9px 4px 4px;
355 355 }
356 356
357 357 #header #header-inner #quick li a:hover {
358 358 background:#4e4e4e url("../images/quick_l_selected.png") no-repeat top left;
359 359 }
360 360
361 361 #header #header-inner #quick li a:hover span {
362 362 border-left:1px solid #545454;
363 363 background:url("../images/quick_r_selected.png") no-repeat top right;
364 364 }
365 365
366 366 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short {
367 367 border-left:none;
368 368 border-right:1px solid #464646;
369 369 background:url("../images/quick_l_selected.png") no-repeat top left;
370 370 }
371 371
372 372
373 373 #header #header-inner #quick ul {
374 374 top:29px;
375 375 right:0;
376 376 min-width:200px;
377 377 display:none;
378 378 position:absolute;
379 379 background:#FFF;
380 380 border:1px solid #666;
381 381 border-top:1px solid #003367;
382 382 z-index:100;
383 383 margin:0;
384 384 padding:0;
385 385 }
386 386
387 387 #header #header-inner #quick ul.repo_switcher {
388 388 max-height:275px;
389 389 overflow-x:hidden;
390 390 overflow-y:auto;
391 391 }
392 392 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
393 393 float:none;
394 394 margin:0;
395 395 border-bottom:2px solid #003367;
396 396 }
397 397
398 398
399 399 #header #header-inner #quick .repo_switcher_type{
400 400 position:absolute;
401 401 left:0;
402 402 top:9px;
403 403
404 404 }
405 405 #header #header-inner #quick li ul li {
406 406 border-bottom:1px solid #ddd;
407 407 }
408 408
409 409 #header #header-inner #quick li ul li a {
410 410 width:182px;
411 411 height:auto;
412 412 display:block;
413 413 float:left;
414 414 background:#FFF;
415 415 color:#003367;
416 416 font-weight:400;
417 417 margin:0;
418 418 padding:7px 9px;
419 419 }
420 420
421 421 #header #header-inner #quick li ul li a:hover {
422 422 color:#000;
423 423 background:#FFF;
424 424 }
425 425
426 426 #header #header-inner #quick ul ul {
427 427 top:auto;
428 428 }
429 429
430 430 #header #header-inner #quick li ul ul {
431 431 right:200px;
432 432 max-height:275px;
433 433 overflow:auto;
434 434 overflow-x:hidden;
435 435 white-space:normal;
436 436 }
437 437
438 438 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover {
439 439 background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFF;
440 440 width:167px;
441 441 margin:0;
442 442 padding:12px 9px 7px 24px;
443 443 }
444 444
445 445 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover {
446 446 background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFF;
447 447 min-width:167px;
448 448 margin:0;
449 449 padding:12px 9px 7px 24px;
450 450 }
451 451
452 452 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover {
453 453 background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFF;
454 454 min-width:167px;
455 455 margin:0;
456 456 padding:12px 9px 7px 24px;
457 457 }
458 458
459 459 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover {
460 460 background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF;
461 461 min-width:167px;
462 462 margin:0 0 0 14px;
463 463 padding:12px 9px 7px 24px;
464 464 }
465 465
466 466 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover {
467 467 background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF;
468 468 min-width:167px;
469 469 margin:0 0 0 14px;
470 470 padding:12px 9px 7px 24px;
471 471 }
472 472
473 473 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover {
474 474 background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF;
475 475 width:167px;
476 476 margin:0;
477 477 padding:12px 9px 7px 24px;
478 478 }
479 479
480 480 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover {
481 481 background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
482 482 width:167px;
483 483 margin:0;
484 484 padding:12px 9px 7px 24px;
485 485 }
486 486
487 487 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover {
488 488 background:#FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
489 489 width:167px;
490 490 margin:0;
491 491 padding:12px 9px 7px 24px;
492 492 }
493 493
494 494 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover {
495 495 background:#FFF url("../images/icons/cog.png") no-repeat 4px 9px;
496 496 width:167px;
497 497 margin:0;
498 498 padding:12px 9px 7px 24px;
499 499 }
500 500
501 501 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover {
502 502 background:#FFF url("../images/icons/key.png") no-repeat 4px 9px;
503 503 width:167px;
504 504 margin:0;
505 505 padding:12px 9px 7px 24px;
506 506 }
507 507
508 508 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover {
509 509 background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
510 510 width:167px;
511 511 margin:0;
512 512 padding:12px 9px 7px 24px;
513 513 }
514 514
515 515 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover {
516 516 background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px;
517 517 width:167px;
518 518 margin:0;
519 519 padding:12px 9px 7px 24px;
520 520 }
521 521
522 522 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover {
523 523 background:#FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
524 524 width:167px;
525 525 margin:0;
526 526 padding:12px 9px 7px 24px;
527 527 }
528 528
529 529 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover {
530 530 background:#FFF url("../images/icons/delete.png") no-repeat 4px 9px;
531 531 width:167px;
532 532 margin:0;
533 533 padding:12px 9px 7px 24px;
534 534 }
535 535
536 536 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover {
537 537 background:#FFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
538 538 width:167px;
539 539 margin:0;
540 540 padding:12px 9px 7px 24px;
541 541 }
542 542
543 543 #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover {
544 544 background:#FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
545 545 width:167px;
546 546 margin:0;
547 547 padding:12px 9px 7px 24px;
548 548 }
549 549
550 550 #header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover {
551 551 background:#FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
552 552 width:167px;
553 553 margin:0;
554 554 padding:12px 9px 7px 24px;
555 555 }
556 556
557 557 #content #left {
558 558 left:0;
559 559 width:280px;
560 560 position:absolute;
561 561 }
562 562
563 563 #content #right {
564 564 margin:0 60px 10px 290px;
565 565 }
566 566
567 567 #content div.box {
568 568 clear:both;
569 569 overflow:hidden;
570 570 background:#fff;
571 571 margin:0 0 10px;
572 572 padding:0 0 10px;
573 573 }
574 574
575 575 #content div.box-left {
576 576 width:49%;
577 577 clear:none;
578 578 float:left;
579 579 margin:0 0 10px;
580 580 }
581 581
582 582 #content div.box-right {
583 583 width:49%;
584 584 clear:none;
585 585 float:right;
586 586 margin:0 0 10px;
587 587 }
588 588
589 589 #content div.box div.title {
590 590 clear:both;
591 591 overflow:hidden;
592 592 background:#369 url("../images/header_inner.png") repeat-x;
593 593 margin:0 0 20px;
594 594 padding:0;
595 595 }
596 596
597 597 #content div.box div.title h5 {
598 598 float:left;
599 599 border:none;
600 600 color:#fff;
601 601 text-transform:uppercase;
602 602 margin:0;
603 603 padding:11px 0 11px 10px;
604 604 }
605 605
606 606 #content div.box div.title ul.links li {
607 607 list-style:none;
608 608 float:left;
609 609 margin:0;
610 610 padding:0;
611 611 }
612 612
613 613 #content div.box div.title ul.links li a {
614 614 border-left: 1px solid #316293;
615 615 color: #FFFFFF;
616 616 display: block;
617 617 float: left;
618 618 font-size: 13px;
619 619 font-weight: 700;
620 620 height: 1%;
621 621 margin: 0;
622 622 padding: 11px 22px 12px;
623 623 text-decoration: none;
624 624 }
625 625
626 626 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6 {
627 627 clear:both;
628 628 overflow:hidden;
629 629 border-bottom:1px solid #DDD;
630 630 margin:10px 20px;
631 631 padding:0 0 15px;
632 632 }
633 633
634 634 #content div.box p {
635 635 color:#5f5f5f;
636 636 font-size:12px;
637 637 line-height:150%;
638 638 margin:0 24px 10px;
639 639 padding:0;
640 640 }
641 641
642 642 #content div.box blockquote {
643 643 border-left:4px solid #DDD;
644 644 color:#5f5f5f;
645 645 font-size:11px;
646 646 line-height:150%;
647 647 margin:0 34px;
648 648 padding:0 0 0 14px;
649 649 }
650 650
651 651 #content div.box blockquote p {
652 652 margin:10px 0;
653 653 padding:0;
654 654 }
655 655
656 656 #content div.box dl {
657 657 margin:10px 24px;
658 658 }
659 659
660 660 #content div.box dt {
661 661 font-size:12px;
662 662 margin:0;
663 663 }
664 664
665 665 #content div.box dd {
666 666 font-size:12px;
667 667 margin:0;
668 668 padding:8px 0 8px 15px;
669 669 }
670 670
671 671 #content div.box li {
672 672 font-size:12px;
673 673 padding:4px 0;
674 674 }
675 675
676 676 #content div.box ul.disc,#content div.box ul.circle {
677 677 margin:10px 24px 10px 38px;
678 678 }
679 679
680 680 #content div.box ul.square {
681 681 margin:10px 24px 10px 40px;
682 682 }
683 683
684 684 #content div.box img.left {
685 685 border:none;
686 686 float:left;
687 687 margin:10px 10px 10px 0;
688 688 }
689 689
690 690 #content div.box img.right {
691 691 border:none;
692 692 float:right;
693 693 margin:10px 0 10px 10px;
694 694 }
695 695
696 696 #content div.box div.messages {
697 697 clear:both;
698 698 overflow:hidden;
699 699 margin:0 20px;
700 700 padding:0;
701 701 }
702 702
703 703 #content div.box div.message {
704 704 clear:both;
705 705 overflow:hidden;
706 706 margin:0;
707 707 padding:10px 0;
708 708 }
709 709
710 710 #content div.box div.message a {
711 711 font-weight:400 !important;
712 712 }
713 713
714 714 #content div.box div.message div.image {
715 715 float:left;
716 716 margin:9px 0 0 5px;
717 717 padding:6px;
718 718 }
719 719
720 720 #content div.box div.message div.image img {
721 721 vertical-align:middle;
722 722 margin:0;
723 723 }
724 724
725 725 #content div.box div.message div.text {
726 726 float:left;
727 727 margin:0;
728 728 padding:9px 6px;
729 729 }
730 730
731 731 #content div.box div.message div.dismiss a {
732 732 height:16px;
733 733 width:16px;
734 734 display:block;
735 735 background:url("../images/icons/cross.png") no-repeat;
736 736 margin:15px 14px 0 0;
737 737 padding:0;
738 738 }
739 739
740 740 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6 {
741 741 border:none;
742 742 margin:0;
743 743 padding:0;
744 744 }
745 745
746 746 #content div.box div.message div.text span {
747 747 height:1%;
748 748 display:block;
749 749 margin:0;
750 750 padding:5px 0 0;
751 751 }
752 752
753 753 #content div.box div.message-error {
754 754 height:1%;
755 755 clear:both;
756 756 overflow:hidden;
757 757 background:#FBE3E4;
758 758 border:1px solid #FBC2C4;
759 759 color:#860006;
760 760 }
761 761
762 762 #content div.box div.message-error h6 {
763 763 color:#860006;
764 764 }
765 765
766 766 #content div.box div.message-warning {
767 767 height:1%;
768 768 clear:both;
769 769 overflow:hidden;
770 770 background:#FFF6BF;
771 771 border:1px solid #FFD324;
772 772 color:#5f5200;
773 773 }
774 774
775 775 #content div.box div.message-warning h6 {
776 776 color:#5f5200;
777 777 }
778 778
779 779 #content div.box div.message-notice {
780 780 height:1%;
781 781 clear:both;
782 782 overflow:hidden;
783 783 background:#8FBDE0;
784 784 border:1px solid #6BACDE;
785 785 color:#003863;
786 786 }
787 787
788 788 #content div.box div.message-notice h6 {
789 789 color:#003863;
790 790 }
791 791
792 792 #content div.box div.message-success {
793 793 height:1%;
794 794 clear:both;
795 795 overflow:hidden;
796 796 background:#E6EFC2;
797 797 border:1px solid #C6D880;
798 798 color:#4e6100;
799 799 }
800 800
801 801 #content div.box div.message-success h6 {
802 802 color:#4e6100;
803 803 }
804 804
805 805 #content div.box div.form div.fields div.field {
806 806 height:1%;
807 807 border-bottom:1px solid #DDD;
808 808 clear:both;
809 809 margin:0;
810 810 padding:10px 0;
811 811 }
812 812
813 813 #content div.box div.form div.fields div.field-first {
814 814 padding:0 0 10px;
815 815 }
816 816
817 817 #content div.box div.form div.fields div.field-noborder {
818 818 border-bottom:0 !important;
819 819 }
820 820
821 821 #content div.box div.form div.fields div.field span.error-message {
822 822 height:1%;
823 823 display:inline-block;
824 824 color:red;
825 825 margin:8px 0 0 4px;
826 826 padding:0;
827 827 }
828 828
829 829 #content div.box div.form div.fields div.field span.success {
830 830 height:1%;
831 831 display:block;
832 832 color:#316309;
833 833 margin:8px 0 0;
834 834 padding:0;
835 835 }
836 836
837 837 #content div.box div.form div.fields div.field div.label {
838 838 left:70px;
839 839 width:auto;
840 840 position:absolute;
841 841 margin:0;
842 842 padding:8px 0 0 5px;
843 843 }
844 844
845 845 #content div.box-left div.form div.fields div.field div.label,#content div.box-right div.form div.fields div.field div.label {
846 846 clear:both;
847 847 overflow:hidden;
848 848 left:0;
849 849 width:auto;
850 850 position:relative;
851 851 margin:0;
852 852 padding:0 0 8px;
853 853 }
854 854
855 855 #content div.box div.form div.fields div.field div.label-select {
856 856 padding:5px 0 0 5px;
857 857 }
858 858
859 859 #content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select {
860 860 padding:0 0 8px;
861 861 }
862 862
863 863 #content div.box-left div.form div.fields div.field div.label-textarea,#content div.box-right div.form div.fields div.field div.label-textarea {
864 864 padding:0 0 8px !important;
865 865 }
866 866
867 867 #content div.box div.form div.fields div.field div.label label, div.label label{
868 868 color:#393939;
869 869 font-weight:700;
870 870 }
871 871
872 872 #content div.box div.form div.fields div.field div.input {
873 873 margin:0 0 0 200px;
874 874 }
875 875 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input {
876 876 margin:0 0 0 0px;
877 877 }
878 878
879 879 #content div.box div.form div.fields div.field div.input input {
880 880 background:#FFF;
881 881 border-top:1px solid #b3b3b3;
882 882 border-left:1px solid #b3b3b3;
883 883 border-right:1px solid #eaeaea;
884 884 border-bottom:1px solid #eaeaea;
885 885 color:#000;
886 886 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
887 887 font-size:11px;
888 888 margin:0;
889 889 padding:7px 7px 6px;
890 890 }
891 891
892 892
893 893
894 894 #content div.box div.form div.fields div.field div.input input.small {
895 895 width:30%;
896 896 }
897 897
898 898 #content div.box div.form div.fields div.field div.input input.medium {
899 899 width:55%;
900 900 }
901 901
902 902 #content div.box div.form div.fields div.field div.input input.large {
903 903 width:85%;
904 904 }
905 905
906 906 #content div.box div.form div.fields div.field div.input input.date {
907 907 width:177px;
908 908 }
909 909
910 910 #content div.box div.form div.fields div.field div.input input.button {
911 911 background:#D4D0C8;
912 912 border-top:1px solid #FFF;
913 913 border-left:1px solid #FFF;
914 914 border-right:1px solid #404040;
915 915 border-bottom:1px solid #404040;
916 916 color:#000;
917 917 margin:0;
918 918 padding:4px 8px;
919 919 }
920 920
921 921 #content div.box div.form div.fields div.field div.textarea {
922 922 border-top:1px solid #b3b3b3;
923 923 border-left:1px solid #b3b3b3;
924 924 border-right:1px solid #eaeaea;
925 925 border-bottom:1px solid #eaeaea;
926 926 margin:0 0 0 200px;
927 927 padding:10px;
928 928 }
929 929
930 930 #content div.box div.form div.fields div.field div.textarea-editor {
931 931 border:1px solid #ddd;
932 932 padding:0;
933 933 }
934 934
935 935 #content div.box div.form div.fields div.field div.textarea textarea {
936 936 width:100%;
937 937 height:220px;
938 938 overflow:hidden;
939 939 background:#FFF;
940 940 color:#000;
941 941 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
942 942 font-size:11px;
943 943 outline:none;
944 944 border-width:0;
945 945 margin:0;
946 946 padding:0;
947 947 }
948 948
949 949 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea {
950 950 width:100%;
951 951 height:100px;
952 952 }
953 953
954 954 #content div.box div.form div.fields div.field div.textarea table {
955 955 width:100%;
956 956 border:none;
957 957 margin:0;
958 958 padding:0;
959 959 }
960 960
961 961 #content div.box div.form div.fields div.field div.textarea table td {
962 962 background:#DDD;
963 963 border:none;
964 964 padding:0;
965 965 }
966 966
967 967 #content div.box div.form div.fields div.field div.textarea table td table {
968 968 width:auto;
969 969 border:none;
970 970 margin:0;
971 971 padding:0;
972 972 }
973 973
974 974 #content div.box div.form div.fields div.field div.textarea table td table td {
975 975 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
976 976 font-size:11px;
977 977 padding:5px 5px 5px 0;
978 978 }
979 979
980 980 #content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus {
981 981 background:#f6f6f6;
982 982 border-color:#666;
983 983 }
984 984
985 985 div.form div.fields div.field div.button {
986 986 margin:0;
987 987 padding:0 0 0 8px;
988 988 }
989 989
990 990 div.form div.fields div.field div.highlight .ui-button {
991 991 background:#4e85bb url("../images/button_highlight.png") repeat-x;
992 992 border-top:1px solid #5c91a4;
993 993 border-left:1px solid #2a6f89;
994 994 border-right:1px solid #2b7089;
995 995 border-bottom:1px solid #1a6480;
996 996 color:#FFF;
997 997 margin:0;
998 998 padding:6px 12px;
999 999 }
1000 1000
1001 1001 div.form div.fields div.field div.highlight .ui-state-hover {
1002 1002 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
1003 1003 border-top:1px solid #78acbf;
1004 1004 border-left:1px solid #34819e;
1005 1005 border-right:1px solid #35829f;
1006 1006 border-bottom:1px solid #257897;
1007 1007 color:#FFF;
1008 1008 margin:0;
1009 1009 padding:6px 12px;
1010 1010 }
1011 1011
1012 1012 #content div.box div.form div.fields div.buttons div.highlight input.ui-button {
1013 1013 background:#4e85bb url("../images/button_highlight.png") repeat-x;
1014 1014 border-top:1px solid #5c91a4;
1015 1015 border-left:1px solid #2a6f89;
1016 1016 border-right:1px solid #2b7089;
1017 1017 border-bottom:1px solid #1a6480;
1018 1018 color:#fff;
1019 1019 margin:0;
1020 1020 padding:6px 12px;
1021 1021 }
1022 1022
1023 1023 #content div.box div.form div.fields div.buttons div.highlight input.ui-state-hover {
1024 1024 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
1025 1025 border-top:1px solid #78acbf;
1026 1026 border-left:1px solid #34819e;
1027 1027 border-right:1px solid #35829f;
1028 1028 border-bottom:1px solid #257897;
1029 1029 color:#fff;
1030 1030 margin:0;
1031 1031 padding:6px 12px;
1032 1032 }
1033 1033
1034 1034 #content div.box table {
1035 1035 width:100%;
1036 1036 border-collapse:collapse;
1037 1037 margin:0;
1038 1038 padding:0;
1039 1039 }
1040 1040
1041 1041 #content div.box table th {
1042 1042 background:#eee;
1043 1043 border-bottom:1px solid #ddd;
1044 1044 padding:5px 0px 5px 5px;
1045 1045 }
1046 1046
1047 1047 #content div.box table th.left {
1048 1048 text-align:left;
1049 1049 }
1050 1050
1051 1051 #content div.box table th.right {
1052 1052 text-align:right;
1053 1053 }
1054 1054
1055 1055 #content div.box table th.center {
1056 1056 text-align:center;
1057 1057 }
1058 1058
1059 1059 #content div.box table th.selected {
1060 1060 vertical-align:middle;
1061 1061 padding:0;
1062 1062 }
1063 1063
1064 1064 #content div.box table td {
1065 1065 background:#fff;
1066 1066 border-bottom:1px solid #cdcdcd;
1067 1067 vertical-align:middle;
1068 1068 padding:5px;
1069 1069 }
1070 1070
1071 1071 #content div.box table tr.selected td {
1072 1072 background:#FFC;
1073 1073 }
1074 1074
1075 1075 #content div.box table td.selected {
1076 1076 width:3%;
1077 1077 text-align:center;
1078 1078 vertical-align:middle;
1079 1079 padding:0;
1080 1080 }
1081 1081
1082 1082 #content div.box table td.action {
1083 1083 width:45%;
1084 1084 text-align:left;
1085 1085 }
1086 1086
1087 1087 #content div.box table td.date {
1088 1088 width:33%;
1089 1089 text-align:center;
1090 1090 }
1091 1091
1092 1092 #content div.box div.action {
1093 1093 float:right;
1094 1094 background:#FFF;
1095 1095 text-align:right;
1096 1096 margin:10px 0 0;
1097 1097 padding:0;
1098 1098 }
1099 1099
1100 1100 #content div.box div.action select {
1101 1101 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1102 1102 font-size:11px;
1103 1103 margin:0;
1104 1104 }
1105 1105
1106 1106 #content div.box div.action .ui-selectmenu {
1107 1107 margin:0;
1108 1108 padding:0;
1109 1109 }
1110 1110
1111 1111 #content div.box div.pagination {
1112 1112 height:1%;
1113 1113 clear:both;
1114 1114 overflow:hidden;
1115 1115 margin:10px 0 0;
1116 1116 padding:0;
1117 1117 }
1118 1118
1119 1119 #content div.box div.pagination ul.pager {
1120 1120 float:right;
1121 1121 text-align:right;
1122 1122 margin:0;
1123 1123 padding:0;
1124 1124 }
1125 1125
1126 1126 #content div.box div.pagination ul.pager li {
1127 1127 height:1%;
1128 1128 float:left;
1129 1129 list-style:none;
1130 1130 background:#ebebeb url("../images/pager.png") repeat-x;
1131 1131 border-top:1px solid #dedede;
1132 1132 border-left:1px solid #cfcfcf;
1133 1133 border-right:1px solid #c4c4c4;
1134 1134 border-bottom:1px solid #c4c4c4;
1135 1135 color:#4A4A4A;
1136 1136 font-weight:700;
1137 1137 margin:0 0 0 4px;
1138 1138 padding:0;
1139 1139 }
1140 1140
1141 1141 #content div.box div.pagination ul.pager li.separator {
1142 1142 padding:6px;
1143 1143 }
1144 1144
1145 1145 #content div.box div.pagination ul.pager li.current {
1146 1146 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1147 1147 border-top:1px solid #ccc;
1148 1148 border-left:1px solid #bebebe;
1149 1149 border-right:1px solid #b1b1b1;
1150 1150 border-bottom:1px solid #afafaf;
1151 1151 color:#515151;
1152 1152 padding:6px;
1153 1153 }
1154 1154
1155 1155 #content div.box div.pagination ul.pager li a {
1156 1156 height:1%;
1157 1157 display:block;
1158 1158 float:left;
1159 1159 color:#515151;
1160 1160 text-decoration:none;
1161 1161 margin:0;
1162 1162 padding:6px;
1163 1163 }
1164 1164
1165 1165 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active {
1166 1166 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1167 1167 border-top:1px solid #ccc;
1168 1168 border-left:1px solid #bebebe;
1169 1169 border-right:1px solid #b1b1b1;
1170 1170 border-bottom:1px solid #afafaf;
1171 1171 margin:-1px;
1172 1172 }
1173 1173
1174 1174 #content div.box div.pagination-wh {
1175 1175 height:1%;
1176 1176 clear:both;
1177 1177 overflow:hidden;
1178 1178 text-align:right;
1179 1179 margin:10px 0 0;
1180 1180 padding:0;
1181 1181 }
1182 1182
1183 1183 #content div.box div.pagination-right {
1184 1184 float:right;
1185 1185 }
1186 1186
1187 1187 #content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot {
1188 1188 height:1%;
1189 1189 float:left;
1190 1190 background:#ebebeb url("../images/pager.png") repeat-x;
1191 1191 border-top:1px solid #dedede;
1192 1192 border-left:1px solid #cfcfcf;
1193 1193 border-right:1px solid #c4c4c4;
1194 1194 border-bottom:1px solid #c4c4c4;
1195 1195 color:#4A4A4A;
1196 1196 font-weight:700;
1197 1197 margin:0 0 0 4px;
1198 1198 padding:6px;
1199 1199 }
1200 1200
1201 1201 #content div.box div.pagination-wh span.pager_curpage {
1202 1202 height:1%;
1203 1203 float:left;
1204 1204 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1205 1205 border-top:1px solid #ccc;
1206 1206 border-left:1px solid #bebebe;
1207 1207 border-right:1px solid #b1b1b1;
1208 1208 border-bottom:1px solid #afafaf;
1209 1209 color:#515151;
1210 1210 font-weight:700;
1211 1211 margin:0 0 0 4px;
1212 1212 padding:6px;
1213 1213 }
1214 1214
1215 1215 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active {
1216 1216 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1217 1217 border-top:1px solid #ccc;
1218 1218 border-left:1px solid #bebebe;
1219 1219 border-right:1px solid #b1b1b1;
1220 1220 border-bottom:1px solid #afafaf;
1221 1221 text-decoration:none;
1222 1222 }
1223 1223
1224 1224 #content div.box div.traffic div.legend {
1225 1225 clear:both;
1226 1226 overflow:hidden;
1227 1227 border-bottom:1px solid #ddd;
1228 1228 margin:0 0 10px;
1229 1229 padding:0 0 10px;
1230 1230 }
1231 1231
1232 1232 #content div.box div.traffic div.legend h6 {
1233 1233 float:left;
1234 1234 border:none;
1235 1235 margin:0;
1236 1236 padding:0;
1237 1237 }
1238 1238
1239 1239 #content div.box div.traffic div.legend li {
1240 1240 list-style:none;
1241 1241 float:left;
1242 1242 font-size:11px;
1243 1243 margin:0;
1244 1244 padding:0 8px 0 4px;
1245 1245 }
1246 1246
1247 1247 #content div.box div.traffic div.legend li.visits {
1248 1248 border-left:12px solid #edc240;
1249 1249 }
1250 1250
1251 1251 #content div.box div.traffic div.legend li.pageviews {
1252 1252 border-left:12px solid #afd8f8;
1253 1253 }
1254 1254
1255 1255 #content div.box div.traffic table {
1256 1256 width:auto;
1257 1257 }
1258 1258
1259 1259 #content div.box div.traffic table td {
1260 1260 background:transparent;
1261 1261 border:none;
1262 1262 padding:2px 3px 3px;
1263 1263 }
1264 1264
1265 1265 #content div.box div.traffic table td.legendLabel {
1266 1266 padding:0 3px 2px;
1267 1267 }
1268 1268
1269 1269 #footer {
1270 1270 clear:both;
1271 1271 overflow:hidden;
1272 1272 text-align:right;
1273 1273 margin:0;
1274 1274 padding:0 10px 4px;
1275 1275 margin:-10px 0 0;
1276 1276 }
1277 1277
1278 1278 #footer div#footer-inner {
1279 1279 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
1280 1280 border-top:2px solid #FFFFFF;
1281 1281 }
1282 1282
1283 1283 #footer div#footer-inner p {
1284 1284 padding:15px 25px 15px 0;
1285 1285 color:#FFF;
1286 1286 font-weight:700;
1287 1287 }
1288 1288 #footer div#footer-inner .footer-link {
1289 1289 float:left;
1290 1290 padding-left:10px;
1291 1291 }
1292 1292 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a {
1293 1293 color:#FFF;
1294 1294 }
1295 1295
1296 1296 #login div.title {
1297 1297 width:420px;
1298 1298 clear:both;
1299 1299 overflow:hidden;
1300 1300 position:relative;
1301 1301 background:#003367 url("../images/header_inner.png") repeat-x;
1302 1302 margin:0 auto;
1303 1303 padding:0;
1304 1304 }
1305 1305
1306 1306 #login div.inner {
1307 1307 width:380px;
1308 1308 background:#FFF url("../images/login.png") no-repeat top left;
1309 1309 border-top:none;
1310 1310 border-bottom:none;
1311 1311 margin:0 auto;
1312 1312 padding:20px;
1313 1313 }
1314 1314
1315 1315 #login div.form div.fields div.field div.label {
1316 1316 width:173px;
1317 1317 float:left;
1318 1318 text-align:right;
1319 1319 margin:2px 10px 0 0;
1320 1320 padding:5px 0 0 5px;
1321 1321 }
1322 1322
1323 1323 #login div.form div.fields div.field div.input input {
1324 1324 width:176px;
1325 1325 background:#FFF;
1326 1326 border-top:1px solid #b3b3b3;
1327 1327 border-left:1px solid #b3b3b3;
1328 1328 border-right:1px solid #eaeaea;
1329 1329 border-bottom:1px solid #eaeaea;
1330 1330 color:#000;
1331 1331 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1332 1332 font-size:11px;
1333 1333 margin:0;
1334 1334 padding:7px 7px 6px;
1335 1335 }
1336 1336
1337 1337 #login div.form div.fields div.buttons {
1338 1338 clear:both;
1339 1339 overflow:hidden;
1340 1340 border-top:1px solid #DDD;
1341 1341 text-align:right;
1342 1342 margin:0;
1343 1343 padding:10px 0 0;
1344 1344 }
1345 1345
1346 1346 #login div.form div.links {
1347 1347 clear:both;
1348 1348 overflow:hidden;
1349 1349 margin:10px 0 0;
1350 1350 padding:0 0 2px;
1351 1351 }
1352 1352
1353 1353 #register div.title {
1354 1354 clear:both;
1355 1355 overflow:hidden;
1356 1356 position:relative;
1357 1357 background:#003367 url("../images/header_inner.png") repeat-x;
1358 1358 margin:0 auto;
1359 1359 padding:0;
1360 1360 }
1361 1361
1362 1362 #register div.inner {
1363 1363 background:#FFF;
1364 1364 border-top:none;
1365 1365 border-bottom:none;
1366 1366 margin:0 auto;
1367 1367 padding:20px;
1368 1368 }
1369 1369
1370 1370 #register div.form div.fields div.field div.label {
1371 1371 width:135px;
1372 1372 float:left;
1373 1373 text-align:right;
1374 1374 margin:2px 10px 0 0;
1375 1375 padding:5px 0 0 5px;
1376 1376 }
1377 1377
1378 1378 #register div.form div.fields div.field div.input input {
1379 1379 width:300px;
1380 1380 background:#FFF;
1381 1381 border-top:1px solid #b3b3b3;
1382 1382 border-left:1px solid #b3b3b3;
1383 1383 border-right:1px solid #eaeaea;
1384 1384 border-bottom:1px solid #eaeaea;
1385 1385 color:#000;
1386 1386 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1387 1387 font-size:11px;
1388 1388 margin:0;
1389 1389 padding:7px 7px 6px;
1390 1390 }
1391 1391
1392 1392 #register div.form div.fields div.buttons {
1393 1393 clear:both;
1394 1394 overflow:hidden;
1395 1395 border-top:1px solid #DDD;
1396 1396 text-align:left;
1397 1397 margin:0;
1398 1398 padding:10px 0 0 150px;
1399 1399 }
1400 1400
1401 1401 #register div.form div.fields div.buttons div.highlight input.ui-button {
1402 1402 background:url("../images/button_highlight.png") repeat-x scroll 0 0 #4E85BB;
1403 1403 color:#FFF;
1404 1404 border-color:#5C91A4 #2B7089 #1A6480 #2A6F89;
1405 1405 border-style:solid;
1406 1406 border-width:1px;
1407 1407 }
1408 1408
1409 1409 #register div.form div.activation_msg {
1410 1410 padding-top:4px;
1411 1411 padding-bottom:4px;
1412 1412 }
1413 1413
1414 1414 #journal .journal_day{
1415 1415 font-size:20px;
1416 1416 padding:10px 0px;
1417 1417 border-bottom:2px solid #DDD;
1418 1418 margin-left:10px;
1419 1419 margin-right:10px;
1420 1420 }
1421 1421
1422 1422 #journal .journal_container{
1423 1423 padding:5px;
1424 1424 clear:both;
1425 1425 margin:0px 5px 0px 10px;
1426 1426 }
1427 1427
1428 1428 #journal .journal_action_container{
1429 1429 padding-left:38px;
1430 1430 }
1431 1431
1432 1432 #journal .journal_user{
1433 1433 color: #747474;
1434 1434 font-size: 14px;
1435 1435 font-weight: bold;
1436 1436 height: 30px;
1437 1437 }
1438 1438 #journal .journal_icon{
1439 1439 clear: both;
1440 1440 float: left;
1441 1441 padding-right: 4px;
1442 1442 padding-top: 3px;
1443 1443 }
1444 1444 #journal .journal_action{
1445 1445 padding-top:4px;
1446 1446 min-height:2px;
1447 1447 float:left
1448 1448 }
1449 1449 #journal .journal_action_params{
1450 1450 clear: left;
1451 1451 padding-left: 22px;
1452 1452 }
1453 1453 #journal .journal_repo{
1454 1454 float: left;
1455 1455 margin-left: 6px;
1456 1456 padding-top: 3px;
1457 1457 }
1458 1458 #journal .date{
1459 1459 clear: both;
1460 1460 color: #777777;
1461 1461 font-size: 11px;
1462 1462 padding-left: 22px;
1463 1463 }
1464 1464 #journal .journal_repo .journal_repo_name{
1465 1465 font-weight: bold;
1466 1466 font-size: 1.1em;
1467 1467 }
1468 1468 #journal .compare_view{
1469 1469 padding: 5px 0px 5px 0px;
1470 1470 width: 95px;
1471 1471 }
1472 1472 .journal_highlight{
1473 1473 font-weight: bold;
1474 1474 padding: 0 2px;
1475 1475 vertical-align: bottom;
1476 1476 }
1477 1477 .trending_language_tbl,.trending_language_tbl td {
1478 1478 border:0 !important;
1479 1479 margin:0 !important;
1480 1480 padding:0 !important;
1481 1481 }
1482 1482
1483 1483 .trending_language {
1484 1484 background-color:#003367;
1485 1485 color:#FFF;
1486 1486 display:block;
1487 1487 min-width:20px;
1488 1488 text-decoration:none;
1489 1489 height:12px;
1490 1490 margin-bottom:4px;
1491 1491 margin-left:5px;
1492 1492 white-space:pre;
1493 1493 padding:3px;
1494 1494 }
1495 1495
1496 1496 h3.files_location {
1497 1497 font-size:1.8em;
1498 1498 font-weight:700;
1499 1499 border-bottom:none !important;
1500 1500 margin:10px 0 !important;
1501 1501 }
1502 1502
1503 1503 #files_data dl dt {
1504 1504 float:left;
1505 1505 width:115px;
1506 1506 margin:0 !important;
1507 1507 padding:5px;
1508 1508 }
1509 1509
1510 1510 #files_data dl dd {
1511 1511 margin:0 !important;
1512 1512 padding:5px !important;
1513 1513 }
1514 1514
1515 1515 #changeset_content {
1516 1516 border:1px solid #CCC;
1517 1517 padding:5px;
1518 1518 }
1519 1519 #changeset_compare_view_content{
1520 1520 border:1px solid #CCC;
1521 1521 padding:5px;
1522 1522 }
1523 1523
1524 1524 #changeset_content .container {
1525 1525 min-height:120px;
1526 1526 font-size:1.2em;
1527 1527 overflow:hidden;
1528 1528 }
1529 1529
1530 1530 #changeset_compare_view_content .compare_view_commits{
1531 1531 width: auto !important;
1532 1532 }
1533 1533
1534 1534 #changeset_compare_view_content .compare_view_commits td{
1535 1535 padding:0px 0px 0px 12px !important;
1536 1536 }
1537 1537
1538 1538 #changeset_content .container .right {
1539 1539 float:right;
1540 1540 width:25%;
1541 1541 text-align:right;
1542 1542 }
1543 1543
1544 1544 #changeset_content .container .left .message {
1545 1545 font-style:italic;
1546 1546 color:#556CB5;
1547 1547 white-space:pre-wrap;
1548 1548 }
1549 1549
1550 1550 .cs_files .cur_cs{
1551 1551 margin:10px 2px;
1552 1552 font-weight: bold;
1553 1553 }
1554 1554
1555 1555 .cs_files .node{
1556 1556 float: left;
1557 1557 }
1558 1558 .cs_files .changes{
1559 1559 float: right;
1560 1560 }
1561 1561 .cs_files .changes .added{
1562 1562 background-color: #BBFFBB;
1563 1563 float: left;
1564 1564 text-align: center;
1565 1565 font-size: 90%;
1566 1566 }
1567 1567 .cs_files .changes .deleted{
1568 1568 background-color: #FF8888;
1569 1569 float: left;
1570 1570 text-align: center;
1571 1571 font-size: 90%;
1572 1572 }
1573 1573 .cs_files .cs_added {
1574 1574 background:url("../images/icons/page_white_add.png") no-repeat scroll 3px;
1575 1575 height:16px;
1576 1576 padding-left:20px;
1577 1577 margin-top:7px;
1578 1578 text-align:left;
1579 1579 }
1580 1580
1581 1581 .cs_files .cs_changed {
1582 1582 background:url("../images/icons/page_white_edit.png") no-repeat scroll 3px;
1583 1583 height:16px;
1584 1584 padding-left:20px;
1585 1585 margin-top:7px;
1586 1586 text-align:left;
1587 1587 }
1588 1588
1589 1589 .cs_files .cs_removed {
1590 1590 background:url("../images/icons/page_white_delete.png") no-repeat scroll 3px;
1591 1591 height:16px;
1592 1592 padding-left:20px;
1593 1593 margin-top:7px;
1594 1594 text-align:left;
1595 1595 }
1596 1596
1597 1597 #graph {
1598 1598 overflow:hidden;
1599 1599 }
1600 1600
1601 1601 #graph_nodes {
1602 1602 width:160px;
1603 1603 float:left;
1604 1604 margin-left:-50px;
1605 1605 margin-top:5px;
1606 1606 }
1607 1607
1608 1608 #graph_content {
1609 1609 width:800px;
1610 1610 float:left;
1611 1611 }
1612 1612
1613 1613 #graph_content .container_header {
1614 1614 border:1px solid #CCC;
1615 1615 padding:10px;
1616 1616 }
1617 1617 #graph_content #rev_range_container{
1618 1618 padding:10px 0px;
1619 1619 }
1620 1620 #graph_content .container {
1621 1621 border-bottom:1px solid #CCC;
1622 1622 border-left:1px solid #CCC;
1623 1623 border-right:1px solid #CCC;
1624 1624 min-height:80px;
1625 1625 overflow:hidden;
1626 1626 font-size:1.2em;
1627 1627 }
1628 1628
1629 1629 #graph_content .container .right {
1630 1630 float:right;
1631 1631 width:28%;
1632 1632 text-align:right;
1633 1633 padding-bottom:5px;
1634 1634 }
1635 1635
1636 1636 #graph_content .container .left .date {
1637 1637 font-weight:700;
1638 1638 padding-bottom:5px;
1639 1639 }
1640 1640 #graph_content .container .left .date span{
1641 1641 vertical-align: text-top;
1642 1642 }
1643 1643
1644 1644 #graph_content .container .left .message {
1645 1645 font-size:100%;
1646 1646 padding-top:3px;
1647 1647 white-space:pre-wrap;
1648 1648 }
1649 1649
1650 1650 .right div {
1651 1651 clear:both;
1652 1652 }
1653 1653
1654 1654 .right .changes .added,.changed,.removed {
1655 1655 border:1px solid #DDD;
1656 1656 display:block;
1657 1657 float:right;
1658 1658 text-align:center;
1659 1659 min-width:15px;
1660 1660 cursor: help;
1661 1661 }
1662 1662 .right .changes .large {
1663 1663 border:1px solid #DDD;
1664 1664 display:block;
1665 1665 float:right;
1666 1666 text-align:center;
1667 1667 min-width:45px;
1668 1668 cursor: help;
1669 1669 background: #54A9F7;
1670 1670 }
1671 1671
1672 1672 .right .changes .added {
1673 1673 background:#BFB;
1674 1674 }
1675 1675
1676 1676 .right .changes .changed {
1677 1677 background:#FD8;
1678 1678 }
1679 1679
1680 1680 .right .changes .removed {
1681 1681 background:#F88;
1682 1682 }
1683 1683
1684 1684 .right .merge {
1685 1685 vertical-align:top;
1686 1686 font-size:0.75em;
1687 1687 font-weight:700;
1688 1688 }
1689 1689
1690 1690 .right .parent {
1691 1691 font-size:90%;
1692 1692 font-family:monospace;
1693 1693 }
1694 1694
1695 1695 .right .logtags .branchtag {
1696 1696 background:#FFF url("../images/icons/arrow_branch.png") no-repeat right 6px;
1697 1697 display:block;
1698 1698 font-size:0.8em;
1699 1699 padding:11px 16px 0 0;
1700 1700 }
1701 1701
1702 1702 .right .logtags .tagtag {
1703 1703 background:#FFF url("../images/icons/tag_blue.png") no-repeat right 6px;
1704 1704 display:block;
1705 1705 font-size:0.8em;
1706 1706 padding:11px 16px 0 0;
1707 1707 }
1708 1708
1709 1709 div.browserblock {
1710 1710 overflow:hidden;
1711 1711 border:1px solid #ccc;
1712 1712 background:#f8f8f8;
1713 1713 font-size:100%;
1714 1714 line-height:125%;
1715 1715 padding:0;
1716 1716 }
1717 1717
1718 1718 div.browserblock .browser-header {
1719 1719 background:#FFF;
1720 1720 padding:10px 0px 25px 0px;
1721 1721 width: 100%;
1722 1722 }
1723 1723 div.browserblock .browser-nav {
1724 1724 float:left
1725 1725 }
1726 1726
1727 1727 div.browserblock .browser-branch {
1728 1728 float:left;
1729 1729 }
1730 1730
1731 1731 div.browserblock .browser-branch label {
1732 1732 color:#4A4A4A;
1733 1733 vertical-align:text-top;
1734 1734 }
1735 1735
1736 1736 div.browserblock .browser-header span {
1737 1737 margin-left:5px;
1738 1738 font-weight:700;
1739 1739 }
1740 1740
1741 1741 div.browserblock .browser-body {
1742 1742 background:#EEE;
1743 1743 border-top:1px solid #CCC;
1744 1744 }
1745 1745
1746 1746 table.code-browser {
1747 1747 border-collapse:collapse;
1748 1748 width:100%;
1749 1749 }
1750 1750
1751 1751 table.code-browser tr {
1752 1752 margin:3px;
1753 1753 }
1754 1754
1755 1755 table.code-browser thead th {
1756 1756 background-color:#EEE;
1757 1757 height:20px;
1758 1758 font-size:1.1em;
1759 1759 font-weight:700;
1760 1760 text-align:left;
1761 1761 padding-left:10px;
1762 1762 }
1763 1763
1764 1764 table.code-browser tbody td {
1765 1765 padding-left:10px;
1766 1766 height:20px;
1767 1767 }
1768 1768
1769 1769 table.code-browser .browser-file {
1770 1770 background:url("../images/icons/document_16.png") no-repeat scroll 3px;
1771 1771 height:16px;
1772 1772 padding-left:20px;
1773 1773 text-align:left;
1774 1774 }
1775 1775 .diffblock .changeset_file{
1776 1776 background:url("../images/icons/file.png") no-repeat scroll 3px;
1777 1777 height:16px;
1778 1778 padding-left:22px;
1779 1779 text-align:left;
1780 1780 font-size: 14px;
1781 1781 }
1782 1782
1783 1783 .diffblock .changeset_header{
1784 1784 margin-left: 6px !important;
1785 1785 }
1786 1786
1787 1787 table.code-browser .browser-dir {
1788 1788 background:url("../images/icons/folder_16.png") no-repeat scroll 3px;
1789 1789 height:16px;
1790 1790 padding-left:20px;
1791 1791 text-align:left;
1792 1792 }
1793 1793
1794 1794 .box .search {
1795 1795 clear:both;
1796 1796 overflow:hidden;
1797 1797 margin:0;
1798 1798 padding:0 20px 10px;
1799 1799 }
1800 1800
1801 1801 .box .search div.search_path {
1802 1802 background:none repeat scroll 0 0 #EEE;
1803 1803 border:1px solid #CCC;
1804 1804 color:blue;
1805 1805 margin-bottom:10px;
1806 1806 padding:10px 0;
1807 1807 }
1808 1808
1809 1809 .box .search div.search_path div.link {
1810 1810 font-weight:700;
1811 1811 margin-left:25px;
1812 1812 }
1813 1813
1814 1814 .box .search div.search_path div.link a {
1815 1815 color:#003367;
1816 1816 cursor:pointer;
1817 1817 text-decoration:none;
1818 1818 }
1819 1819
1820 1820 #path_unlock {
1821 1821 color:red;
1822 1822 font-size:1.2em;
1823 1823 padding-left:4px;
1824 1824 }
1825 1825
1826 1826 .info_box span {
1827 1827 margin-left:3px;
1828 1828 margin-right:3px;
1829 1829 }
1830 1830
1831 1831 .info_box .rev {
1832 1832 color: #003367;
1833 1833 font-size: 1.6em;
1834 1834 font-weight: bold;
1835 1835 vertical-align: sub;
1836 1836 }
1837 1837
1838 1838
1839 1839 .info_box input#at_rev,.info_box input#size {
1840 1840 background:#FFF;
1841 1841 border-top:1px solid #b3b3b3;
1842 1842 border-left:1px solid #b3b3b3;
1843 1843 border-right:1px solid #eaeaea;
1844 1844 border-bottom:1px solid #eaeaea;
1845 1845 color:#000;
1846 1846 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1847 1847 font-size:12px;
1848 1848 margin:0;
1849 1849 padding:1px 5px 1px;
1850 1850 }
1851 1851
1852 1852
1853 1853
1854 1854 .info_box input#view {
1855 1855 text-align:center;
1856 1856 padding:4px 3px 2px 2px;
1857 1857 }
1858 1858
1859 1859 .yui-overlay,.yui-panel-container {
1860 1860 visibility:hidden;
1861 1861 position:absolute;
1862 1862 z-index:2;
1863 1863 }
1864 1864
1865 1865 .yui-tt {
1866 1866 visibility:hidden;
1867 1867 position:absolute;
1868 1868 color:#666;
1869 1869 background-color:#FFF;
1870 1870 font-family:arial, helvetica, verdana, sans-serif;
1871 1871 border:2px solid #003367;
1872 1872 font:100% sans-serif;
1873 1873 width:auto;
1874 1874 opacity:1px;
1875 1875 padding:8px;
1876 1876 white-space: pre;
1877 1877 -webkit-border-radius: 8px 8px 8px 8px;
1878 1878 -khtml-border-radius: 8px 8px 8px 8px;
1879 1879 -moz-border-radius: 8px 8px 8px 8px;
1880 1880 border-radius: 8px 8px 8px 8px;
1881 1881 }
1882 1882
1883 1883 .ac {
1884 1884 vertical-align:top;
1885 1885 }
1886 1886
1887 1887 .ac .yui-ac {
1888 1888 position:relative;
1889 1889 font-family:arial;
1890 1890 font-size:100%;
1891 1891 }
1892 1892
1893 1893 .ac .perm_ac {
1894 1894 width:15em;
1895 1895 }
1896 1896
1897 1897 .ac .yui-ac-input {
1898 1898 width:100%;
1899 1899 }
1900 1900
1901 1901 .ac .yui-ac-container {
1902 1902 position:absolute;
1903 1903 top:1.6em;
1904 1904 width:100%;
1905 1905 }
1906 1906
1907 1907 .ac .yui-ac-content {
1908 1908 position:absolute;
1909 1909 width:100%;
1910 1910 border:1px solid gray;
1911 1911 background:#fff;
1912 1912 overflow:hidden;
1913 1913 z-index:9050;
1914 1914 }
1915 1915
1916 1916 .ac .yui-ac-shadow {
1917 1917 position:absolute;
1918 1918 width:100%;
1919 1919 background:#000;
1920 1920 -moz-opacity:0.1px;
1921 1921 opacity:.10;
1922 1922 filter:alpha(opacity = 10);
1923 1923 z-index:9049;
1924 1924 margin:.3em;
1925 1925 }
1926 1926
1927 1927 .ac .yui-ac-content ul {
1928 1928 width:100%;
1929 1929 margin:0;
1930 1930 padding:0;
1931 1931 }
1932 1932
1933 1933 .ac .yui-ac-content li {
1934 1934 cursor:default;
1935 1935 white-space:nowrap;
1936 1936 margin:0;
1937 1937 padding:2px 5px;
1938 1938 }
1939 1939
1940 1940 .ac .yui-ac-content li.yui-ac-prehighlight {
1941 1941 background:#B3D4FF;
1942 1942 }
1943 1943
1944 1944 .ac .yui-ac-content li.yui-ac-highlight {
1945 1945 background:#556CB5;
1946 1946 color:#FFF;
1947 1947 }
1948 1948
1949 1949
1950 1950 .follow{
1951 1951 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
1952 1952 height: 16px;
1953 1953 width: 20px;
1954 1954 cursor: pointer;
1955 1955 display: block;
1956 1956 float: right;
1957 1957 margin-top: 2px;
1958 1958 }
1959 1959
1960 1960 .following{
1961 1961 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
1962 1962 height: 16px;
1963 1963 width: 20px;
1964 1964 cursor: pointer;
1965 1965 display: block;
1966 1966 float: right;
1967 1967 margin-top: 2px;
1968 1968 }
1969 1969
1970 1970 .currently_following{
1971 1971 padding-left: 10px;
1972 1972 padding-bottom:5px;
1973 1973 }
1974 1974
1975 1975 .add_icon {
1976 1976 background:url("../images/icons/add.png") no-repeat scroll 3px;
1977 1977 padding-left:20px;
1978 1978 padding-top:0px;
1979 1979 text-align:left;
1980 1980 }
1981 1981
1982 1982 .edit_icon {
1983 1983 background:url("../images/icons/folder_edit.png") no-repeat scroll 3px;
1984 1984 padding-left:20px;
1985 1985 padding-top:0px;
1986 1986 text-align:left;
1987 1987 }
1988 1988
1989 1989 .delete_icon {
1990 1990 background:url("../images/icons/delete.png") no-repeat scroll 3px;
1991 1991 padding-left:20px;
1992 1992 padding-top:0px;
1993 1993 text-align:left;
1994 1994 }
1995 1995
1996 1996 .refresh_icon {
1997 1997 background:url("../images/icons/arrow_refresh.png") no-repeat scroll 3px;
1998 1998 padding-left:20px;
1999 1999 padding-top:0px;
2000 2000 text-align:left;
2001 2001 }
2002 2002
2003 2003 .pull_icon {
2004 2004 background:url("../images/icons/connect.png") no-repeat scroll 3px;
2005 2005 padding-left:20px;
2006 2006 padding-top:0px;
2007 2007 text-align:left;
2008 2008 }
2009 2009
2010 2010 .rss_icon {
2011 2011 background:url("../images/icons/rss_16.png") no-repeat scroll 3px;
2012 2012 padding-left:20px;
2013 2013 padding-top:0px;
2014 2014 text-align:left;
2015 2015 }
2016 2016
2017 2017 .atom_icon {
2018 2018 background:url("../images/icons/atom.png") no-repeat scroll 3px;
2019 2019 padding-left:20px;
2020 2020 padding-top:0px;
2021 2021 text-align:left;
2022 2022 }
2023 2023
2024 2024 .archive_icon {
2025 2025 background:url("../images/icons/compress.png") no-repeat scroll 3px;
2026 2026 padding-left:20px;
2027 2027 text-align:left;
2028 2028 padding-top:1px;
2029 2029 }
2030 2030
2031 2031 .start_following_icon {
2032 2032 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2033 2033 padding-left:20px;
2034 2034 text-align:left;
2035 2035 padding-top:0px;
2036 2036 }
2037 2037
2038 2038 .stop_following_icon {
2039 2039 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2040 2040 padding-left:20px;
2041 2041 text-align:left;
2042 2042 padding-top:0px;
2043 2043 }
2044 2044
2045 2045 .action_button {
2046 2046 border:0;
2047 2047 display:block;
2048 2048 }
2049 2049
2050 2050 .action_button:hover {
2051 2051 border:0;
2052 2052 text-decoration:underline;
2053 2053 cursor:pointer;
2054 2054 }
2055 2055
2056 2056 #switch_repos {
2057 2057 position:absolute;
2058 2058 height:25px;
2059 2059 z-index:1;
2060 2060 }
2061 2061
2062 2062 #switch_repos select {
2063 2063 min-width:150px;
2064 2064 max-height:250px;
2065 2065 z-index:1;
2066 2066 }
2067 2067
2068 2068 .breadcrumbs {
2069 2069 border:medium none;
2070 2070 color:#FFF;
2071 2071 float:left;
2072 2072 text-transform:uppercase;
2073 2073 font-weight:700;
2074 2074 font-size:14px;
2075 2075 margin:0;
2076 2076 padding:11px 0 11px 10px;
2077 2077 }
2078 2078
2079 2079 .breadcrumbs a {
2080 2080 color:#FFF;
2081 2081 }
2082 2082
2083 2083 .flash_msg ul {
2084 2084 margin:0;
2085 2085 padding:0 0 10px;
2086 2086 }
2087 2087
2088 2088 .error_msg {
2089 2089 background-color:#FFCFCF;
2090 2090 background-image:url("../images/icons/error_msg.png");
2091 2091 border:1px solid #FF9595;
2092 2092 color:#C30;
2093 2093 }
2094 2094
2095 2095 .warning_msg {
2096 2096 background-color:#FFFBCC;
2097 2097 background-image:url("../images/icons/warning_msg.png");
2098 2098 border:1px solid #FFF35E;
2099 2099 color:#C69E00;
2100 2100 }
2101 2101
2102 2102 .success_msg {
2103 2103 background-color:#D5FFCF;
2104 2104 background-image:url("../images/icons/success_msg.png");
2105 2105 border:1px solid #97FF88;
2106 2106 color:#090;
2107 2107 }
2108 2108
2109 2109 .notice_msg {
2110 2110 background-color:#DCE3FF;
2111 2111 background-image:url("../images/icons/notice_msg.png");
2112 2112 border:1px solid #93A8FF;
2113 2113 color:#556CB5;
2114 2114 }
2115 2115
2116 2116 .success_msg,.error_msg,.notice_msg,.warning_msg {
2117 2117 background-position:10px center;
2118 2118 background-repeat:no-repeat;
2119 2119 font-size:12px;
2120 2120 font-weight:700;
2121 2121 min-height:14px;
2122 2122 line-height:14px;
2123 2123 margin-bottom:0;
2124 2124 margin-top:0;
2125 2125 display:block;
2126 2126 overflow:auto;
2127 2127 padding:6px 10px 6px 40px;
2128 2128 }
2129 2129
2130 2130 #msg_close {
2131 2131 background:transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
2132 2132 cursor:pointer;
2133 2133 height:16px;
2134 2134 position:absolute;
2135 2135 right:5px;
2136 2136 top:5px;
2137 2137 width:16px;
2138 2138 }
2139 2139
2140 2140 div#legend_container table,div#legend_choices table {
2141 2141 width:auto !important;
2142 2142 }
2143 2143
2144 2144 table#permissions_manage {
2145 2145 width:0 !important;
2146 2146 }
2147 2147
2148 2148 table#permissions_manage span.private_repo_msg {
2149 2149 font-size:0.8em;
2150 2150 opacity:0.6px;
2151 2151 }
2152 2152
2153 2153 table#permissions_manage td.private_repo_msg {
2154 2154 font-size:0.8em;
2155 2155 }
2156 2156
2157 2157 table#permissions_manage tr#add_perm_input td {
2158 2158 vertical-align:middle;
2159 2159 }
2160 2160
2161 2161 div.gravatar {
2162 2162 background-color:#FFF;
2163 2163 border:1px solid #D0D0D0;
2164 2164 float:left;
2165 2165 margin-right:0.7em;
2166 2166 padding:2px 2px 0;
2167 2167 }
2168 2168
2169 2169 #header,#content,#footer {
2170 2170 min-width:978px;
2171 2171 }
2172 2172
2173 2173 #content {
2174 2174 min-height:100%;
2175 2175 clear:both;
2176 2176 overflow:hidden;
2177 2177 padding:14px 10px;
2178 2178 }
2179 2179
2180 2180 #content div.box div.title div.search {
2181 2181 background:url("../images/title_link.png") no-repeat top left;
2182 2182 border-left:1px solid #316293;
2183 2183 }
2184 2184
2185 2185 #content div.box div.title div.search div.input input {
2186 2186 border:1px solid #316293;
2187 2187 }
2188 2188
2189 2189 #content div.box div.title div.search div.button input.ui-button {
2190 2190 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2191 2191 border:1px solid #316293;
2192 2192 border-left:none;
2193 2193 color:#FFF;
2194 2194 }
2195 2195
2196 2196 #content div.box input.ui-button-small {
2197 2197 background:#e5e3e3 url("../images/button.png") repeat-x;
2198 2198 border-top:1px solid #DDD;
2199 2199 border-left:1px solid #c6c6c6;
2200 2200 border-right:1px solid #DDD;
2201 2201 border-bottom:1px solid #c6c6c6;
2202 2202 color:#515151;
2203 2203 outline:none;
2204 2204 margin:0;
2205 2205 }
2206 2206
2207 #content div.box input.ui-button-small-blue {
2208 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2209 border-top:1px solid #5c91a4;
2210 border-left:1px solid #2a6f89;
2211 border-right:1px solid #2b7089;
2212 border-bottom:1px solid #1a6480;
2213 color:#fff;
2214 }
2215
2207 2216 #content div.box input.ui-button-small submit,button{
2208 2217 cursor: pointer;
2209 2218 }
2210 2219
2211 2220 #content div.box div.title div.search div.button input.ui-state-hover {
2212 2221 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
2213 2222 border:1px solid #316293;
2214 2223 border-left:none;
2215 2224 color:#FFF;
2216 2225 }
2217 2226
2218 2227 #content div.box div.form div.fields div.field div.highlight .ui-button {
2219 2228 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2220 2229 border-top:1px solid #5c91a4;
2221 2230 border-left:1px solid #2a6f89;
2222 2231 border-right:1px solid #2b7089;
2223 2232 border-bottom:1px solid #1a6480;
2224 2233 color:#fff;
2225 2234 }
2226 2235
2227 2236 #content div.box div.form div.fields div.field div.highlight .ui-state-hover {
2228 2237 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
2229 2238 border-top:1px solid #78acbf;
2230 2239 border-left:1px solid #34819e;
2231 2240 border-right:1px solid #35829f;
2232 2241 border-bottom:1px solid #257897;
2233 2242 color:#fff;
2234 2243 }
2235 2244
2236 2245 ins,div.options a:hover {
2237 2246 text-decoration:none;
2238 2247 }
2239 2248
2240 2249 img,#header #header-inner #quick li a:hover span.normal,#header #header-inner #quick li ul li.last,#content div.box div.form div.fields div.field div.textarea table td table td a,#clone_url {
2241 2250 border:none;
2242 2251 }
2243 2252
2244 2253 img.icon,.right .merge img {
2245 2254 vertical-align:bottom;
2246 2255 }
2247 2256
2248 2257 #header ul#logged-user,#content div.box div.title ul.links,#content div.box div.message div.dismiss,#content div.box div.traffic div.legend ul {
2249 2258 float:right;
2250 2259 margin:0;
2251 2260 padding:0;
2252 2261 }
2253 2262
2254 2263 #header #header-inner #home,#header #header-inner #logo,#content div.box ul.left,#content div.box ol.left,#content div.box div.pagination-left,div#commit_history,div#legend_data,div#legend_container,div#legend_choices {
2255 2264 float:left;
2256 2265 }
2257 2266
2258 2267 #header #header-inner #quick li:hover ul ul,#header #header-inner #quick li:hover ul ul ul,#header #header-inner #quick li:hover ul ul ul ul,#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow {
2259 2268 display:none;
2260 2269 }
2261 2270
2262 2271 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded {
2263 2272 display:block;
2264 2273 }
2265 2274
2266 2275 #content div.graph{
2267 2276 padding:0 10px 10px;
2268 2277 }
2269 2278
2270 2279 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a {
2271 2280 color:#bfe3ff;
2272 2281 }
2273 2282
2274 2283 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal {
2275 2284 margin:10px 24px 10px 44px;
2276 2285 }
2277 2286
2278 2287 #content div.box div.form,#content div.box div.table,#content div.box div.traffic {
2279 2288 clear:both;
2280 2289 overflow:hidden;
2281 2290 margin:0;
2282 2291 padding:0 20px 10px;
2283 2292 }
2284 2293
2285 2294 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields {
2286 2295 clear:both;
2287 2296 overflow:hidden;
2288 2297 margin:0;
2289 2298 padding:0;
2290 2299 }
2291 2300
2292 2301 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span {
2293 2302 height:1%;
2294 2303 display:block;
2295 2304 color:#363636;
2296 2305 margin:0;
2297 2306 padding:2px 0 0;
2298 2307 }
2299 2308
2300 2309 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error {
2301 2310 background:#FBE3E4;
2302 2311 border-top:1px solid #e1b2b3;
2303 2312 border-left:1px solid #e1b2b3;
2304 2313 border-right:1px solid #FBC2C4;
2305 2314 border-bottom:1px solid #FBC2C4;
2306 2315 }
2307 2316
2308 2317 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success {
2309 2318 background:#E6EFC2;
2310 2319 border-top:1px solid #cebb98;
2311 2320 border-left:1px solid #cebb98;
2312 2321 border-right:1px solid #c6d880;
2313 2322 border-bottom:1px solid #c6d880;
2314 2323 }
2315 2324
2316 2325 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input {
2317 2326 margin:0;
2318 2327 }
2319 2328
2320 2329 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios{
2321 2330 margin:0 0 0 0px !important;
2322 2331 padding:0;
2323 2332 }
2324 2333
2325 2334 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios {
2326 2335 margin:0 0 0 200px;
2327 2336 padding:0;
2328 2337 }
2329 2338
2330 2339
2331 2340 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover {
2332 2341 color:#000;
2333 2342 text-decoration:none;
2334 2343 }
2335 2344
2336 2345 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus {
2337 2346 border:1px solid #666;
2338 2347 }
2339 2348
2340 2349 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio {
2341 2350 clear:both;
2342 2351 overflow:hidden;
2343 2352 margin:0;
2344 2353 padding:8px 0 2px;
2345 2354 }
2346 2355
2347 2356 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input {
2348 2357 float:left;
2349 2358 margin:0;
2350 2359 }
2351 2360
2352 2361 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label {
2353 2362 height:1%;
2354 2363 display:block;
2355 2364 float:left;
2356 2365 margin:2px 0 0 4px;
2357 2366 }
2358 2367
2359 2368 div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input {
2360 2369 color:#000;
2361 2370 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2362 2371 font-size:11px;
2363 2372 font-weight:700;
2364 2373 margin:0;
2365 2374 }
2366 2375
2367 2376 div.form div.fields div.field div.button .ui-button,#content div.box div.form div.fields div.buttons input.ui-button {
2368 2377 background:#e5e3e3 url("../images/button.png") repeat-x;
2369 2378 border-top:1px solid #DDD;
2370 2379 border-left:1px solid #c6c6c6;
2371 2380 border-right:1px solid #DDD;
2372 2381 border-bottom:1px solid #c6c6c6;
2373 2382 color:#515151;
2374 2383 outline:none;
2375 2384 margin:0;
2376 2385 padding:6px 12px;
2377 2386 }
2378 2387
2379 2388 div.form div.fields div.field div.button .ui-state-hover,#content div.box div.form div.fields div.buttons input.ui-state-hover {
2380 2389 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2381 2390 border-top:1px solid #ccc;
2382 2391 border-left:1px solid #bebebe;
2383 2392 border-right:1px solid #b1b1b1;
2384 2393 border-bottom:1px solid #afafaf;
2385 2394 color:#515151;
2386 2395 outline:none;
2387 2396 margin:0;
2388 2397 padding:6px 12px;
2389 2398 }
2390 2399
2391 2400 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight {
2392 2401 display:inline;
2393 2402 }
2394 2403
2395 2404 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons {
2396 2405 margin:10px 0 0 200px;
2397 2406 padding:0;
2398 2407 }
2399 2408
2400 2409 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons {
2401 2410 margin:10px 0 0;
2402 2411 }
2403 2412
2404 2413 #content div.box table td.user,#content div.box table td.address {
2405 2414 width:10%;
2406 2415 text-align:center;
2407 2416 }
2408 2417
2409 2418 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link {
2410 2419 text-align:right;
2411 2420 margin:6px 0 0;
2412 2421 padding:0;
2413 2422 }
2414 2423
2415 2424 #content div.box div.action div.button input.ui-button,#login div.form div.fields div.buttons input.ui-button,#register div.form div.fields div.buttons input.ui-button {
2416 2425 background:#e5e3e3 url("../images/button.png") repeat-x;
2417 2426 border-top:1px solid #DDD;
2418 2427 border-left:1px solid #c6c6c6;
2419 2428 border-right:1px solid #DDD;
2420 2429 border-bottom:1px solid #c6c6c6;
2421 2430 color:#515151;
2422 2431 margin:0;
2423 2432 padding:6px 12px;
2424 2433 }
2425 2434
2426 2435 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover {
2427 2436 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2428 2437 border-top:1px solid #ccc;
2429 2438 border-left:1px solid #bebebe;
2430 2439 border-right:1px solid #b1b1b1;
2431 2440 border-bottom:1px solid #afafaf;
2432 2441 color:#515151;
2433 2442 margin:0;
2434 2443 padding:6px 12px;
2435 2444 }
2436 2445
2437 2446 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results {
2438 2447 text-align:left;
2439 2448 float:left;
2440 2449 margin:0;
2441 2450 padding:0;
2442 2451 }
2443 2452
2444 2453 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span {
2445 2454 height:1%;
2446 2455 display:block;
2447 2456 float:left;
2448 2457 background:#ebebeb url("../images/pager.png") repeat-x;
2449 2458 border-top:1px solid #dedede;
2450 2459 border-left:1px solid #cfcfcf;
2451 2460 border-right:1px solid #c4c4c4;
2452 2461 border-bottom:1px solid #c4c4c4;
2453 2462 color:#4A4A4A;
2454 2463 font-weight:700;
2455 2464 margin:0;
2456 2465 padding:6px 8px;
2457 2466 }
2458 2467
2459 2468 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled {
2460 2469 color:#B4B4B4;
2461 2470 padding:6px;
2462 2471 }
2463 2472
2464 2473 #login,#register {
2465 2474 width:520px;
2466 2475 margin:10% auto 0;
2467 2476 padding:0;
2468 2477 }
2469 2478
2470 2479 #login div.color,#register div.color {
2471 2480 clear:both;
2472 2481 overflow:hidden;
2473 2482 background:#FFF;
2474 2483 margin:10px auto 0;
2475 2484 padding:3px 3px 3px 0;
2476 2485 }
2477 2486
2478 2487 #login div.color a,#register div.color a {
2479 2488 width:20px;
2480 2489 height:20px;
2481 2490 display:block;
2482 2491 float:left;
2483 2492 margin:0 0 0 3px;
2484 2493 padding:0;
2485 2494 }
2486 2495
2487 2496 #login div.title h5,#register div.title h5 {
2488 2497 color:#fff;
2489 2498 margin:10px;
2490 2499 padding:0;
2491 2500 }
2492 2501
2493 2502 #login div.form div.fields div.field,#register div.form div.fields div.field {
2494 2503 clear:both;
2495 2504 overflow:hidden;
2496 2505 margin:0;
2497 2506 padding:0 0 10px;
2498 2507 }
2499 2508
2500 2509 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message {
2501 2510 height:1%;
2502 2511 display:block;
2503 2512 color:red;
2504 2513 margin:8px 0 0;
2505 2514 padding:0;
2506 2515 max-width: 320px;
2507 2516 }
2508 2517
2509 2518 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label {
2510 2519 color:#000;
2511 2520 font-weight:700;
2512 2521 }
2513 2522
2514 2523 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input {
2515 2524 float:left;
2516 2525 margin:0;
2517 2526 padding:0;
2518 2527 }
2519 2528
2520 2529 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox {
2521 2530 margin:0 0 0 184px;
2522 2531 padding:0;
2523 2532 }
2524 2533
2525 2534 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label {
2526 2535 color:#565656;
2527 2536 font-weight:700;
2528 2537 }
2529 2538
2530 2539 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input {
2531 2540 color:#000;
2532 2541 font-size:1em;
2533 2542 font-weight:700;
2534 2543 font-family:Verdana, Helvetica, Sans-Serif;
2535 2544 margin:0;
2536 2545 }
2537 2546
2538 2547 #changeset_content .container .wrapper,#graph_content .container .wrapper {
2539 2548 width:600px;
2540 2549 }
2541 2550
2542 2551 #changeset_content .container .left,#graph_content .container .left {
2543 2552 float:left;
2544 2553 width:70%;
2545 2554 padding-left:5px;
2546 2555 }
2547 2556
2548 2557 #changeset_content .container .left .date,.ac .match {
2549 2558 font-weight:700;
2550 2559 padding-top: 5px;
2551 2560 padding-bottom:5px;
2552 2561 }
2553 2562
2554 2563 div#legend_container table td,div#legend_choices table td {
2555 2564 border:none !important;
2556 2565 height:20px !important;
2557 2566 padding:0 !important;
2558 2567 }
2559 2568
2560 2569 #q_filter{
2561 2570 border:0 none;
2562 2571 color:#AAAAAA;
2563 2572 margin-bottom:-4px;
2564 2573 margin-top:-4px;
2565 2574 padding-left:3px;
2566 2575 }
2567 2576
@@ -1,355 +1,352 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <!-- HEADER -->
5 5 <div id="header">
6 6 <!-- user -->
7 7 <ul id="logged-user">
8 8 <li class="first">
9 9 <div class="gravatar">
10 10 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
11 11 </div>
12 12 <div class="account">
13 13 %if c.rhodecode_user.username == 'default':
14 14 <a href="${h.url('public_journal')}">${_('Public journal')}</a>
15 15 %else:
16 16 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
17 17 %endif
18 18 </div>
19 19 </li>
20 20 <li>
21 21 <a href="${h.url('home')}">${_('Home')}</a>
22 22 </li>
23 23 %if c.rhodecode_user.username != 'default':
24 24 <li>
25 25 <a href="${h.url('journal')}">${_('Journal')}</a>
26 26 ##(${c.unread_journal}
27 27 </li>
28 28 %endif
29 29 %if c.rhodecode_user.username == 'default':
30 30 <li class="last highlight">${h.link_to(u'Login',h.url('login_home'))}</li>
31 31 %else:
32 32 <li class="last highlight">${h.link_to(u'Log Out',h.url('logout_home'))}</li>
33 33 %endif
34 34 </ul>
35 35 <!-- end user -->
36 36 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
37 37 <div id="logo">
38 38 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
39 39 </div>
40 40 <!-- MENU -->
41 41 ${self.page_nav()}
42 42 <!-- END MENU -->
43 43 ${self.body()}
44 44 </div>
45 45 </div>
46 46 <!-- END HEADER -->
47 47
48 48 <!-- CONTENT -->
49 49 <div id="content">
50 50 <div class="flash_msg">
51 51 <% messages = h.flash.pop_messages() %>
52 52 % if messages:
53 53 <ul id="flash-messages">
54 54 % for message in messages:
55 55 <li class="${message.category}_msg">${message}</li>
56 56 % endfor
57 57 </ul>
58 58 % endif
59 59 </div>
60 60 <div id="main">
61 61 ${next.main()}
62 62 </div>
63 63 </div>
64 64 <!-- END CONTENT -->
65 65
66 66 <!-- FOOTER -->
67 67 <div id="footer">
68 68 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
69 69 <div>
70 70 <p class="footer-link">
71 71 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
72 72 </p>
73 73 <p class="footer-link-right">
74 74 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
75 75 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
76 76 </p>
77 77 </div>
78 78 </div>
79 79 <script type="text/javascript">
80 80 function tooltip_activate(){
81 81 ${h.tooltip.activate()}
82 82 }
83 83 tooltip_activate();
84 84 </script>
85 85 </div>
86 86 <!-- END FOOTER -->
87 87
88 88 ### MAKO DEFS ###
89 89 <%def name="page_nav()">
90 90 ${self.menu()}
91 91 </%def>
92 92
93 93 <%def name="breadcrumbs()">
94 94 <div class="breadcrumbs">
95 95 ${self.breadcrumbs_links()}
96 96 </div>
97 97 </%def>
98 98
99 99
100 100 <%def name="menu(current=None)">
101 101 <%
102 102 def is_current(selected):
103 103 if selected == current:
104 104 return h.literal('class="current"')
105 105 %>
106 106 %if current not in ['home','admin']:
107 107 ##REGULAR MENU
108 108 <ul id="quick">
109 109 <!-- repo switcher -->
110 110 <li>
111 111 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
112 112 <span class="icon">
113 113 <img src="${h.url("/images/icons/database.png")}" alt="${_('Products')}" />
114 114 </span>
115 115 <span>&darr;</span>
116 116 </a>
117 117 <ul id="repo_switcher_list" class="repo_switcher">
118 118 <li>
119 119 <a href="#">${_('loading...')}</a>
120 120 </li>
121 121 </ul>
122 122 <script type="text/javascript">
123 123 YUE.on('repo_switcher','mouseover',function(){
124 124 function qfilter(){
125 125 var S = YAHOO.util.Selector;
126 126
127 127 var q_filter = YUD.get('q_filter_rs');
128 128 var F = YAHOO.namespace('q_filter_rs');
129 129
130 130 YUE.on(q_filter,'click',function(){
131 131 q_filter.value = '';
132 132 });
133 133
134 134 F.filterTimeout = null;
135 135
136 136 F.updateFilter = function() {
137 137 // Reset timeout
138 138 F.filterTimeout = null;
139 139
140 140 var obsolete = [];
141 141 var nodes = S.query('ul#repo_switcher_list li a.repo_name');
142 142 var req = YUD.get('q_filter_rs').value;
143 143 for (n in nodes){
144 144 YUD.setStyle(nodes[n].parentNode,'display','')
145 145 }
146 146 if (req){
147 147 for (n in nodes){
148 148 console.log(n);
149 149 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
150 150 obsolete.push(nodes[n]);
151 151 }
152 152 }
153 153 if(obsolete){
154 154 for (n in obsolete){
155 155 YUD.setStyle(obsolete[n].parentNode,'display','none');
156 156 }
157 157 }
158 158 }
159 159 }
160 160
161 161 YUE.on(q_filter,'keyup',function(e){
162 162 clearTimeout(F.filterTimeout);
163 163 setTimeout(F.updateFilter,600);
164 164 });
165 165 }
166 166 var loaded = YUD.hasClass('repo_switcher','loaded');
167 167 if(!loaded){
168 168 YUD.addClass('repo_switcher','loaded');
169 169 YAHOO.util.Connect.asyncRequest('GET',"${h.url('repo_switcher')}",{
170 170 success:function(o){
171 171 YUD.get('repo_switcher_list').innerHTML = o.responseText;
172 172 qfilter();
173 173 },
174 174 failure:function(o){
175 175 YUD.removeClass('repo_switcher','loaded');
176 176 }
177 177 },null);
178 178 }
179 179 return false;
180 180 });
181 181 </script>
182 182 </li>
183 183
184 184 <li ${is_current('summary')}>
185 185 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
186 186 <span class="icon">
187 187 <img src="${h.url("/images/icons/clipboard_16.png")}" alt="${_('Summary')}" />
188 188 </span>
189 189 <span>${_('Summary')}</span>
190 190 </a>
191 191 </li>
192 192 ##<li ${is_current('shortlog')}>
193 193 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
194 194 ## <span class="icon">
195 195 ## <img src="${h.url("/images/icons/application_view_list.png")}" alt="${_('Shortlog')}" />
196 196 ## </span>
197 197 ## <span>${_('Shortlog')}</span>
198 198 ## </a>
199 199 ##</li>
200 200 <li ${is_current('changelog')}>
201 201 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
202 202 <span class="icon">
203 203 <img src="${h.url("/images/icons/time.png")}" alt="${_('Changelog')}" />
204 204 </span>
205 205 <span>${_('Changelog')}</span>
206 206 </a>
207 207 </li>
208 208
209 209 <li ${is_current('switch_to')}>
210 210 <a title="${_('Switch to')}" href="#">
211 211 <span class="icon">
212 212 <img src="${h.url("/images/icons/arrow_switch.png")}" alt="${_('Switch to')}" />
213 213 </span>
214 214 <span>${_('Switch to')}</span>
215 215 </a>
216 216 <ul>
217 217 <li>
218 218 ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
219 219 <ul>
220 220 %if c.rhodecode_repo.branches.values():
221 221 %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
222 222 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
223 223 %endfor
224 224 %else:
225 225 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
226 226 %endif
227 227 </ul>
228 228 </li>
229 229 <li>
230 230 ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
231 231 <ul>
232 232 %if c.rhodecode_repo.tags.values():
233 233 %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
234 234 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
235 235 %endfor
236 236 %else:
237 237 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
238 238 %endif
239 239 </ul>
240 240 </li>
241 241 </ul>
242 242 </li>
243 243 <li ${is_current('files')}>
244 244 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
245 245 <span class="icon">
246 246 <img src="${h.url("/images/icons/file.png")}" alt="${_('Files')}" />
247 247 </span>
248 248 <span>${_('Files')}</span>
249 249 </a>
250 250 </li>
251 251
252 252 <li ${is_current('options')}>
253 253 <a title="${_('Options')}" href="#">
254 254 <span class="icon">
255 255 <img src="${h.url("/images/icons/table_gear.png")}" alt="${_('Admin')}" />
256 256 </span>
257 257 <span>${_('Options')}</span>
258 258 </a>
259 259 <ul>
260 260 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
261 261 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
262 262 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
263 263 %else:
264 264 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
265 265 %endif
266 266 %endif
267 267 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
268 268 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
269 269
270 270 %if h.HasPermissionAll('hg.admin')('access admin main page'):
271 271 <li>
272 272 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
273 273 <%def name="admin_menu()">
274 274 <ul>
275 275 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
276 276 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
277 277 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
278 278 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
279 279 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
280 280 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
281 281 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
282 282 </ul>
283 283 </%def>
284 284
285 285 ${admin_menu()}
286 286 </li>
287 287 %endif
288
289 288 </ul>
290 289 </li>
291 290
292 291 <li>
293 292 <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
294 293 <span class="icon_short">
295 294 <img src="${h.url("/images/icons/heart.png")}" alt="${_('Followers')}" />
296 295 </span>
297 296 <span id="current_followers_count" class="short">${c.repository_followers}</span>
298 297 </a>
299 298 </li>
300 299 <li>
301 300 <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
302 301 <span class="icon_short">
303 302 <img src="${h.url("/images/icons/arrow_divide.png")}" alt="${_('Forks')}" />
304 303 </span>
305 304 <span class="short">${c.repository_forks}</span>
306 305 </a>
307 306 </li>
308 307
309
310
311 308 </ul>
312 309 %else:
313 310 ##ROOT MENU
314 311 <ul id="quick">
315 312 <li>
316 313 <a title="${_('Home')}" href="${h.url('home')}">
317 314 <span class="icon">
318 315 <img src="${h.url("/images/icons/home_16.png")}" alt="${_('Home')}" />
319 316 </span>
320 317 <span>${_('Home')}</span>
321 318 </a>
322 319 </li>
323 320 %if c.rhodecode_user.username != 'default':
324 321 <li>
325 322 <a title="${_('Journal')}" href="${h.url('journal')}">
326 323 <span class="icon">
327 324 <img src="${h.url("/images/icons/book.png")}" alt="${_('Journal')}" />
328 325 </span>
329 326 <span>${_('Journal')}</span>
330 327 </a>
331 328 </li>
332 329 %endif
333 330 <li>
334 331 <a title="${_('Search')}" href="${h.url('search')}">
335 332 <span class="icon">
336 333 <img src="${h.url("/images/icons/search_16.png")}" alt="${_('Search')}" />
337 334 </span>
338 335 <span>${_('Search')}</span>
339 336 </a>
340 337 </li>
341 338
342 339 %if h.HasPermissionAll('hg.admin')('access admin main page'):
343 340 <li ${is_current('admin')}>
344 341 <a title="${_('Admin')}" href="${h.url('admin_home')}">
345 342 <span class="icon">
346 343 <img src="${h.url("/images/icons/cog_edit.png")}" alt="${_('Admin')}" />
347 344 </span>
348 345 <span>${_('Admin')}</span>
349 346 </a>
350 347 ${admin_menu()}
351 348 </li>
352 349 %endif
353 350 </ul>
354 351 %endif
355 352 </%def> No newline at end of file
@@ -1,126 +1,130 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 3 <html xmlns="http://www.w3.org/1999/xhtml">
4 4 <head>
5 5 <title>${self.title()}</title>
6 6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 7 <meta name="robots" content="index, nofollow"/>
8 8 <link rel="icon" href="${h.url("/images/icons/database_gear.png")}" type="image/png" />
9 9
10 10 <!-- stylesheets -->
11 11 ${self.css()}
12 12
13 13 %if c.ga_code:
14 14 <!-- Analytics -->
15 15 <script type="text/javascript">
16 16 var _gaq = _gaq || [];
17 17 _gaq.push(['_setAccount', '${c.ga_code}']);
18 18 _gaq.push(['_trackPageview']);
19 19
20 20 (function() {
21 21 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
22 22 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
23 23 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
24 24 })();
25 25 </script>
26 26 %endif
27 27
28 28 <!-- scripts -->
29 29 ${self.js()}
30 30
31 31 </head>
32 32
33 33 <body id="body">
34 34 ${next.body()}
35 35 </body>
36 36
37 37 </html>
38 38
39 39 <%def name="css()">
40 40 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
41 41 <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}" />
42 42 <link rel="stylesheet" type="text/css" href="${h.url('/css/diff.css')}" />
43 ${self.css_extra()}
43 44 </%def>
44
45 <%def name="css_extra()">
46 </%def>
45 47 <%def name="js()">
46 48 <script type="text/javascript">
47 49 if (typeof console == "undefined" || typeof console.log == "undefined")
48 50 console = { log: function() {} }
49 51 </script>
50 52
51 53 % if False:
52 54 <script type="text/javascript" src="${h.url('/js/yui/utilities/utilities.js')}"></script>
53 55 <script type="text/javascript" src="${h.url('/js/yui/container/container.js')}"></script>
54 56 <script type="text/javascript" src="${h.url('/js/yui/datasource/datasource.js')}"></script>
55 57 <script type="text/javascript" src="${h.url('/js/yui/autocomplete/autocomplete.js')}"></script>
56 58 <script type="text/javascript" src="${h.url('/js/yui/selector/selector-min.js')}"></script>
57 59 % else:
58 60 <script type="text/javascript" src="${h.url('/js/yui2a.js')}"></script>
59 61 <!--[if IE]>
60 62 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
61 63 <![endif]-->
62 64 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
63 65 % endif
64
66 ${self.js_extra()}
65 67 <script type="text/javascript">
66 68 var YUC = YAHOO.util.Connect;
67 69 var YUD = YAHOO.util.Dom;
68 70 var YUE = YAHOO.util.Event;
69 71 </script>
70 72
71 73 <script type="text/javascript">
72 74 var base_url = "${h.url('toggle_following')}";
73 75 function onSuccess(target){
74 76
75 77 var f = YUD.get(target.id);
76 78 var f_cnt = YUD.get('current_followers_count');
77 79
78 80 if(f.getAttribute('class')=='follow'){
79 81 f.setAttribute('class','following');
80 82 f.setAttribute('title',"${_('Stop following this repository')}");
81 83
82 84 if(f_cnt){
83 85 var cnt = Number(f_cnt.innerHTML)+1;
84 86 f_cnt.innerHTML = cnt;
85 87 }
86 88
87 89 }
88 90 else{
89 91 f.setAttribute('class','follow');
90 92 f.setAttribute('title',"${_('Start following this repository')}");
91 93 if(f_cnt){
92 94 var cnt = Number(f_cnt.innerHTML)+1;
93 95 f_cnt.innerHTML = cnt;
94 96 }
95 97 }
96 98 }
97 99
98 100 function toggleFollowingUser(target,fallows_user_id,token,user_id){
99 101 args = 'follows_user_id='+fallows_user_id;
100 102 args+= '&amp;auth_token='+token;
101 103 if(user_id != undefined){
102 104 args+="&amp;user_id="+user_id;
103 105 }
104 106 YUC.asyncRequest('POST',base_url,{
105 107 success:function(o){
106 108 onSuccess(target);
107 109 }
108 110 },args); return false;
109 111 }
110 112
111 113 function toggleFollowingRepo(target,fallows_repo_id,token,user_id){
112 114
113 115 args = 'follows_repo_id='+fallows_repo_id;
114 116 args+= '&amp;auth_token='+token;
115 117 if(user_id != undefined){
116 118 args+="&amp;user_id="+user_id;
117 119 }
118 120 YUC.asyncRequest('POST',base_url,{
119 121 success:function(o){
120 122 onSuccess(target);
121 123 }
122 124 },args); return false;
123 125 }
124 126 </script>
125 127
128 </%def>
129 <%def name="js_extra()">
126 130 </%def> No newline at end of file
@@ -1,91 +1,95 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('File annotate')} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('annotate')} @ R${c.cs.revision}:${h.short_id(c.cs.raw_id)}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('files')}
17 17 </%def>
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 <ul class="links">
24 24 <li>
25 25 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.cs.branch}</a></span>
26 26 </li>
27 27 </ul>
28 28 </div>
29 29 <div class="table">
30 30 <div id="files_data">
31 31 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
32 32 <dl class="overview">
33 33 <dt>${_('Revision')}</dt>
34 34 <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),
35 35 h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))} </dd>
36 36 <dt>${_('Size')}</dt>
37 37 <dd>${h.format_byte_size(c.file.size,binary=True)}</dd>
38 38 <dt>${_('Mimetype')}</dt>
39 39 <dd>${c.file.mimetype}</dd>
40 40 <dt>${_('Options')}</dt>
41 41 <dd>${h.link_to(_('show source'),
42 42 h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
43 43 / ${h.link_to(_('show as raw'),
44 44 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
45 45 / ${h.link_to(_('download as raw'),
46 46 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
47 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
48 / ${h.link_to(_('edit'),
49 h.url('files_edit_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
50 % endif
47 51 </dd>
48 52 <dt>${_('History')}</dt>
49 53 <dd>
50 54 <div>
51 55 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
52 56 ${h.hidden('diff2',c.file.last_changeset.raw_id)}
53 57 ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
54 58 ${h.submit('diff','diff to revision',class_="ui-button-small")}
55 59 ${h.submit('show_rev','show at revision',class_="ui-button-small")}
56 60 ${h.end_form()}
57 61 </div>
58 62 </dd>
59 63 </dl>
60 64 <div id="body" class="codeblock">
61 65 <div class="code-header">
62 66 <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${h.short_id(c.file.last_changeset.raw_id)}</div>
63 67 <div class="commit">"${c.file.message}"</div>
64 68 </div>
65 69 <div class="code-body">
66 70 %if c.file.is_binary:
67 71 ${_('Binary file (%s)') % c.file.mimetype}
68 72 %else:
69 73 % if c.file.size < c.cut_off_limit:
70 74 ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
71 75 %else:
72 76 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
73 77 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.f_path))}
74 78 %endif
75 79 <script type="text/javascript">
76 80 YAHOO.util.Event.onDOMReady(function(){
77 81 YAHOO.util.Event.addListener('show_rev','click',function(e){
78 82 YAHOO.util.Event.preventDefault(e);
79 83 var cs = YAHOO.util.Dom.get('diff1').value;
80 84 var url = "${h.url('files_annotate_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
81 85 window.location = url;
82 86 });
83 87 });
84 88 </script>
85 89 %endif
86 90 </div>
87 91 </div>
88 92 </div>
89 93 </div>
90 94 </div>
91 95 </%def> No newline at end of file
@@ -1,91 +1,95 b''
1 1 <dl>
2 2 <dt>${_('Revision')}</dt>
3 3 <dd>
4 4 ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,h.short_id(c.files_list.last_changeset.raw_id)),
5 5 h.url('changeset_home',repo_name=c.repo_name,revision=c.files_list.last_changeset.raw_id))}
6 6 </dd>
7 7 <dt>${_('Size')}</dt>
8 8 <dd>${h.format_byte_size(c.files_list.size,binary=True)}</dd>
9 9 <dt>${_('Mimetype')}</dt>
10 10 <dd>${c.files_list.mimetype}</dd>
11 11 <dt>${_('Options')}</dt>
12 12 <dd>${h.link_to(_('show annotation'),
13 13 h.url('files_annotate_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
14 14 / ${h.link_to(_('show as raw'),
15 15 h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
16 16 / ${h.link_to(_('download as raw'),
17 17 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
18 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
19 / ${h.link_to(_('edit'),
20 h.url('files_edit_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
21 % endif
18 22 </dd>
19 23 <dt>${_('History')}</dt>
20 24 <dd>
21 25 <div>
22 26 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
23 27 ${h.hidden('diff2',c.files_list.last_changeset.raw_id)}
24 28 ${h.select('diff1',c.files_list.last_changeset.raw_id,c.file_history)}
25 29 ${h.submit('diff','diff to revision',class_="ui-button-small")}
26 30 ${h.submit('show_rev','show at revision',class_="ui-button-small")}
27 31 ${h.end_form()}
28 32 </div>
29 33 </dd>
30 34 </dl>
31 35
32 36
33 37 <div id="body" class="codeblock">
34 38 <div class="code-header">
35 39 <div class="revision">${c.files_list.name}@r${c.files_list.last_changeset.revision}:${h.short_id(c.files_list.last_changeset.raw_id)}</div>
36 40 <div class="commit">"${c.files_list.last_changeset.message}"</div>
37 41 </div>
38 42 <div class="code-body">
39 43 %if c.files_list.is_binary:
40 44 ${_('Binary file (%s)') % c.files_list.mimetype}
41 45 %else:
42 46 % if c.files_list.size < c.cut_off_limit:
43 47 ${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
44 48 %else:
45 49 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
46 50 h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
47 51 %endif
48 52
49 53 <script type="text/javascript">
50 54 function highlight_lines(lines){
51 55 for(pos in lines){
52 56 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
53 57 }
54 58 }
55 59 page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
56 60 if (page_highlights.length == 2){
57 61 highlight_ranges = page_highlights[1].split(",");
58 62
59 63 var h_lines = [];
60 64 for (pos in highlight_ranges){
61 65 var _range = highlight_ranges[pos].split('-');
62 66 if(_range.length == 2){
63 67 var start = parseInt(_range[0]);
64 68 var end = parseInt(_range[1]);
65 69 if (start < end){
66 70 for(var i=start;i<=end;i++){
67 71 h_lines.push(i);
68 72 }
69 73 }
70 74 }
71 75 else{
72 76 h_lines.push(parseInt(highlight_ranges[pos]));
73 77 }
74 78 }
75 79 highlight_lines(h_lines);
76 80 }
77 81 </script>
78 82 %endif
79 83 </div>
80 84 </div>
81 85
82 86 <script type="text/javascript">
83 87 YAHOO.util.Event.onDOMReady(function(){
84 88 YAHOO.util.Event.addListener('show_rev','click',function(e){
85 89 YAHOO.util.Event.preventDefault(e);
86 90 var cs = YAHOO.util.Dom.get('diff1').value;
87 91 var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
88 92 window.location = url;
89 93 });
90 94 });
91 95 </script> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now