Mercurial Hosting > nabble
comparison src/nabble/view/web/template/NamlEditor.jtp @ 0:7ecd1a4ef557
add content
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 21 Mar 2019 19:15:52 -0600 |
parents | |
children | 18cf4872fd7f |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:7ecd1a4ef557 |
---|---|
1 <% | |
2 package nabble.view.web.template; | |
3 | |
4 import fschmidt.util.java.HtmlUtils; | |
5 import fschmidt.util.servlet.JtpContext; | |
6 import nabble.model.DailyNumber; | |
7 import nabble.model.Node; | |
8 import nabble.model.Site; | |
9 import nabble.model.User; | |
10 import nabble.modules.ModuleManager; | |
11 import nabble.naml.compiler.CompileException; | |
12 import nabble.naml.compiler.Macro; | |
13 import nabble.naml.compiler.Meaning; | |
14 import nabble.naml.compiler.Source; | |
15 import nabble.view.lib.Jtp; | |
16 import nabble.view.lib.Permissions; | |
17 import nabble.view.lib.Shared; | |
18 import org.apache.commons.fileupload.FileItem; | |
19 import org.apache.commons.fileupload.FileUploadException; | |
20 | |
21 import javax.servlet.ServletException; | |
22 import javax.servlet.http.HttpServlet; | |
23 import javax.servlet.http.HttpServletRequest; | |
24 import javax.servlet.http.HttpServletResponse; | |
25 import java.io.ByteArrayOutputStream; | |
26 import java.io.IOException; | |
27 import java.io.InputStream; | |
28 import java.io.PrintWriter; | |
29 import java.io.StringWriter; | |
30 import java.nio.charset.Charset; | |
31 import java.nio.charset.CharsetEncoder; | |
32 import java.util.HashMap; | |
33 import java.util.List; | |
34 import java.util.Map; | |
35 import java.util.TreeMap; | |
36 import java.util.zip.ZipEntry; | |
37 import java.util.zip.ZipInputStream; | |
38 | |
39 | |
40 public final class NamlEditor extends HttpServlet { | |
41 | |
42 protected void service(HttpServletRequest request, HttpServletResponse response) | |
43 throws ServletException, IOException | |
44 { | |
45 User user = Jtp.getUser(request, response); | |
46 if (user == null) { | |
47 simpleLoginPage("You must login to edit the NAML code of this application.", request, response); | |
48 return; | |
49 } | |
50 boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP); | |
51 boolean isSysAdmin = Permissions.isSysAdmin(user); | |
52 | |
53 if (!isSiteAdmin && !isSysAdmin) { | |
54 simpleLoginPage("You must login to edit the NAML code of this application.", request, response); | |
55 return; | |
56 } | |
57 buildPage(request, response); | |
58 } | |
59 | |
60 public static void buildPage(HttpServletRequest request, HttpServletResponse response) | |
61 throws ServletException, IOException | |
62 { | |
63 Site site = Jtp.getSite(request); | |
64 Map<String,String> tweaks = new TreeMap<String,String>(site.getCustomTweaks()); | |
65 Node rootNode = site.getRootNode(); | |
66 Exception exception = site.getTweakException(); | |
67 | |
68 String macroDef = null; | |
69 String selectedFileName = null; | |
70 String overridenBody = null; | |
71 String meaningId = request.getParameter("id"); | |
72 if (meaningId != null) { | |
73 Meaning meaning = site.getProgram().getMeaning(meaningId); | |
74 Macro macro = (Macro) meaning; | |
75 | |
76 String macroType = macro.getType().toString().toLowerCase(); | |
77 String macroBody = macro.element.toString(); | |
78 int posOpen = macroBody.indexOf('<'); | |
79 int posClose = macroBody.indexOf('>', posOpen+1); | |
80 macroDef = macroBody.substring(posOpen, posClose); | |
81 macroDef = macroDef.replaceFirst("<"+macroType, "<override_"+macroType); | |
82 int posClosingTag = macroBody.lastIndexOf("</"); | |
83 String closingTag = "</override_" + macroType + '>'; | |
84 overridenBody = macroDef + macroBody.substring(posClose, posClosingTag) + closingTag; | |
85 for( Map.Entry<String,String> entry : tweaks.entrySet() ) { | |
86 String name = entry.getKey(); | |
87 String content = entry.getValue(); | |
88 if (content.contains(macroDef)) { | |
89 selectedFileName = name; | |
90 break; | |
91 } | |
92 } | |
93 } | |
94 | |
95 String previousUrl = request.getParameter("prev"); | |
96 | |
97 PrintWriter out = response.getWriter(); | |
98 %> | |
99 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |
100 <html> | |
101 <head> | |
102 <META NAME="robots" CONTENT="noindex,nofollow"/> | |
103 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> | |
104 <title>Naml Editor - <%=rootNode.getSubject()%></title> | |
105 <% Shared.head(request, response, false); %> | |
106 <style type="text/css"> | |
107 div.main-content { | |
108 position:absolute; | |
109 top:5.2em; | |
110 bottom:1.3em; | |
111 right:.8em; | |
112 left:.8em; | |
113 } | |
114 div.big-title { | |
115 margin:.5em 0; | |
116 } | |
117 table.vertical-control { | |
118 border-collapse:collapse; | |
119 width:100%; | |
120 height:100%; | |
121 position:absolute; | |
122 top:0; | |
123 bottom:0; | |
124 } | |
125 table.vertical-control td { | |
126 padding:0; | |
127 vertical-align:top; | |
128 } | |
129 table.vertical-control td.options { | |
130 width:13em; | |
131 padding-top:.2em; | |
132 } | |
133 table.vertical-control td.details { | |
134 padding: .2em; | |
135 } | |
136 ul.vertical-control { | |
137 width:100%; | |
138 list-style-type:none; | |
139 padding:0; | |
140 margin:.5em 0; | |
141 } | |
142 ul.vertical-control li { | |
143 padding: .3em; | |
144 white-space:nowrap; | |
145 cursor: pointer; | |
146 font-weight:normal; | |
147 width:13em; | |
148 overflow: hidden; | |
149 border-top-left-radius:5px; | |
150 border-bottom-left-radius:5px; | |
151 -moz-border-radius-topleft:5px; | |
152 -moz-border-radius-bottomleft:5px; | |
153 } | |
154 ul.vertical-control li.selected { | |
155 cursor: default; | |
156 font-weight:bold; | |
157 } | |
158 #options-ul { | |
159 overflow-x:hidden !important; | |
160 overflow-y:auto !important; | |
161 } | |
162 div.more { | |
163 position:absolute; | |
164 left:0; | |
165 width:13em; | |
166 bottom:0; | |
167 border-top: 1px dotted #ddd; | |
168 } | |
169 img.toolbar-icon { | |
170 vertical-align: -25%; | |
171 } | |
172 div.default { | |
173 position: absolute; | |
174 top: .2em; | |
175 bottom: 0; | |
176 margin-bottom: .2em; | |
177 height: auto; | |
178 overflow: auto; | |
179 } | |
180 div.advanced-option-body { | |
181 padding: 0 1.5em 1.5em; | |
182 } | |
183 | |
184 .CodeMirror-line-numbers { | |
185 font-family: verdana, arial, sans-serif; | |
186 font-size: 11pt; | |
187 width: 2.2em; | |
188 color: #aaa; | |
189 background-color: #eee; | |
190 text-align: right; | |
191 padding-right: .3em; | |
192 line-height: normal; | |
193 } | |
194 | |
195 span.xml-tagname {color: #A0B;} | |
196 span.xml-attribute {color: #281;} | |
197 span.xml-punctuation {color: black;} | |
198 span.xml-attname {color: #00F;} | |
199 span.xml-comment {color: #A70;} | |
200 span.xml-cdata {color: #48A;} | |
201 span.xml-processing {color: #999;} | |
202 span.xml-entity {color: #A22;} | |
203 span.xml-error {color: #F00 !important;} | |
204 span.xml-text {color: black;} | |
205 | |
206 div.status-label { | |
207 float:right; | |
208 font-weight:bold; | |
209 width:6em; | |
210 text-align:center; | |
211 line-height:1.2em; | |
212 margin:-.2em 0 0 .5em; | |
213 padding:.35em; | |
214 } | |
215 div.no-error { | |
216 color: #656565; | |
217 cursor: text; | |
218 } | |
219 div.has-error { | |
220 color: white; | |
221 cursor: pointer; | |
222 background: url('/gradients/v20_EE9999_EE5555') #EE5555 repeat-x; | |
223 } | |
224 #error-message { | |
225 position:absolute; | |
226 display:none; | |
227 z-index:1000; | |
228 padding:0 1em 1em; | |
229 margin-top:.2em; | |
230 top:5em; | |
231 left:10%; | |
232 bottom:10%; | |
233 right:.7em; | |
234 overflow:auto; | |
235 display:none; | |
236 } | |
237 </style> | |
238 | |
239 <script src="/util/codemirror/js/codemirror.js"></script> | |
240 <script src="/util/codemirror/js/highlight.js"></script> | |
241 <script src="/util/codemirror/js/stringstream.js"></script> | |
242 <script src="/util/codemirror/js/tokenize.js"></script> | |
243 <script src="/util/codemirror/js/parsexml.js"></script> | |
244 | |
245 <script type="text/javascript"> | |
246 var configFileName = "<%=ModuleManager.CONFIGURATION_TWEAK%>"; | |
247 function setVisible(name, show) { | |
248 if (show) $('#'+name).show(); | |
249 else $('#'+name).hide(); | |
250 }; | |
251 var modified = []; | |
252 function setModified(id) { | |
253 if (modified.indexOf(id) >= 0) | |
254 return; | |
255 modified.push(id); | |
256 if (id == 'removed') | |
257 return; | |
258 var $tab = $('#tab_'+id); | |
259 var html = $tab.html(); | |
260 $tab.html(html.replace(/file\.png/, 'file_modified.png')); | |
261 setVisible('modified-warning',true); | |
262 }; | |
263 function resetModified() { | |
264 for (var i = 0; i < modified.length; i++) { | |
265 if (modified[i] == 'removed') | |
266 continue; | |
267 var $tab = $('#tab_'+modified[i]); | |
268 var html = $tab.html(); | |
269 $tab.html(html.replace(/file_modified\.png/, 'file.png')); | |
270 } | |
271 setVisible('modified-warning',false); | |
272 modified = []; | |
273 } | |
274 window.onbeforeunload = function() { | |
275 if (modified.length > 0) | |
276 return 'You have unsaved changes.\nDo you really want to leave without saving?'; | |
277 }; | |
278 var fileImg = '<img src="/images/file.png" style="vertical-align:-20%"/> '; | |
279 Array.prototype.remove = function(s){ | |
280 for(i=0; i<this.length; i++){ | |
281 if (s == this[i]) | |
282 this.splice(i, 1); | |
283 } | |
284 } | |
285 var advancedOptions = { | |
286 id: 'advanced_options', | |
287 name: 'Advanced Options', | |
288 content: null | |
289 }; | |
290 function isDefaultTab(f) { | |
291 return f == advancedOptions; | |
292 }; | |
293 | |
294 var files = []; | |
295 <% | |
296 int i = 0; | |
297 for( Map.Entry<String,String> entry : tweaks.entrySet() ) { | |
298 String name = entry.getKey(); | |
299 String content = entry.getValue(); | |
300 %> | |
301 files.push({ | |
302 id: <%=i++%>, | |
303 name: "<%=HtmlUtils.javascriptStringEncode(name)%>", | |
304 contents: "<%=HtmlUtils.javascriptStringEncode(content)%>" | |
305 }); | |
306 <% } %> | |
307 function addFile(file) { | |
308 $('#options-ul').append('<li id="tab_'+file.id+'">'+fileImg+file.name+'</li>'); | |
309 clickableTab(file); | |
310 | |
311 var textareaId = 'textarea_'+file.id; | |
312 $('#details').append('<div id="div_'+file.id+'" style="display:none"><textarea id="'+textareaId+'"></textarea></div>'); | |
313 var $textarea = $('#'+textareaId); | |
314 $textarea.val(file.contents); | |
315 | |
316 file.editor = CodeMirror.fromTextArea(textareaId, { | |
317 parserfile: "parsexml.js", | |
318 stylesheet: "/util/codemirror/css/xmlcolors.css", | |
319 path: "/util/codemirror/js/", | |
320 lineNumbers: true, | |
321 indentUnit: 4, | |
322 onChange: function() { setModified(file.id); } | |
323 }); | |
324 | |
325 var $wrapping = $textarea.next(); | |
326 $wrapping.css({ | |
327 position: 'absolute', | |
328 top: '2.5em', | |
329 bottom: 0, | |
330 marginBottom: '.2em', | |
331 height: 'auto' | |
332 }); | |
333 $wrapping.attr('id', 'wrapping_'+file.id) | |
334 $wrapping.addClass('no-bg-color'); | |
335 | |
336 /* File Toolbar */ | |
337 var toolbar = '<div id="toolbar_'+file.id+'" class="shaded-bg-color" style="padding:.2em .4em .3em;text-align:right">'; | |
338 toolbar += '<div class="float-left" style="font-style:italic;padding:.3em">'+file.name+'</div>'; | |
339 toolbar += '<button class="toolbar" style="padding:.1em .3em" onclick="renameFile()"><img class="toolbar-icon" src="/images/edit_sm.png"/> Rename</button> '; | |
340 toolbar += '<button class="toolbar" style="padding:.1em .3em" onclick="removeFile()"><img class="toolbar-icon" src="/images/remove_sm.png"/> Remove</button>'; | |
341 toolbar += '</div>'; | |
342 $textarea.after(toolbar); | |
343 }; | |
344 function clickableTab(f) { | |
345 var $tab = $('#tab_'+f.id); | |
346 $tab.click(function() { | |
347 select(f); | |
348 }); | |
349 tabHover($tab); | |
350 }; | |
351 function tabHover($tab) { | |
352 $tab.hover(function() { | |
353 if (!$(this).hasClass('selected')) | |
354 $(this).addClass('light-bg-color'); | |
355 }, | |
356 function() { | |
357 $(this).removeClass('light-bg-color'); | |
358 } | |
359 ); | |
360 }; | |
361 | |
362 var initialized = []; | |
363 function showFile(file) { | |
364 $('#div_'+file.id).show(); | |
365 if (initialized.indexOf(file.id) == -1) { | |
366 var cc = Nabble.get('toolbar_'+file.id); | |
367 $('#wrapping_'+file.id).css('top', $(cc).outerHeight()); | |
368 var frame = window.frames[window.frames.length-1]; | |
369 var doc = window.document; | |
370 $(frame.document).click(function() { $(doc).click(); }); | |
371 initialized.push(file.id); | |
372 } | |
373 }; | |
374 | |
375 var selected = null; | |
376 function select(f) { | |
377 if (selected != f) { | |
378 $('li').removeClass('selected dark-bg-color'); | |
379 var $tab = $('#tab_'+f.id); | |
380 $tab.addClass('selected dark-bg-color'); | |
381 $('#details').children().hide(); | |
382 showFile(f); | |
383 selected = f; | |
384 layout(); | |
385 } else if (f == null || files.length == 0) | |
386 setVisible('no-selection',true); | |
387 }; | |
388 function layout() { | |
389 if (selected) { | |
390 var detailsWidth = $('#details').width(); | |
391 if (!isDefaultTab(selected)) { | |
392 var $div = $('#div_'+selected.id); | |
393 var $lineNumbers = $('div.CodeMirror-line-numbers', $div); | |
394 var width = detailsWidth - $lineNumbers.width() - 5; | |
395 $('div.CodeMirror-wrapping', $div).width(Math.round(width)+'px'); | |
396 } else { | |
397 $('div.default').width(Math.round(detailsWidth - 30)+'px'); | |
398 } | |
399 } | |
400 var $ul = $('#options-ul'); | |
401 var uTop = $ul.position().top; | |
402 var mTop = $('#more').position().top; | |
403 $ul.height(mTop-uTop-10); | |
404 }; | |
405 function newFile() { | |
406 var name = prompt('Name of the file:'); | |
407 if (name == configFileName) { | |
408 alert('You cannot create a file with this name.'); | |
409 return; | |
410 } | |
411 if (name) { | |
412 var f = { | |
413 id: new Date().getTime(), | |
414 name: name, | |
415 contents: '' | |
416 }; | |
417 files.push(f); | |
418 addFile(f); | |
419 select(f); | |
420 setModified(f.id); | |
421 } | |
422 }; | |
423 function renameFile() { | |
424 if (selected) { | |
425 name = prompt("Name of the file:", selected.name); | |
426 if (name && name !='null' && Nabble.trim(name).length > 0) { | |
427 selected.name = Nabble.trim(name); | |
428 $tab = $('#tab_'+selected.id).html(fileImg + name); | |
429 $toolbar = $('#toolbar_'+selected.id+' div').html(name); | |
430 setModified(selected.id); | |
431 } | |
432 } | |
433 }; | |
434 function removeFile() { | |
435 if (selected && confirm("Do you really want to delete " + selected.name + "?")) { | |
436 var i = files.indexOf(selected); | |
437 files.remove(selected); | |
438 setModified('removed'); | |
439 $('#tab_'+selected.id+',#div_'+selected.id).remove(); | |
440 i = i > 0? i-1 : files.length > 0? 0 : -1; | |
441 if (i == -1) | |
442 setVisible('no-selection',true); | |
443 else | |
444 select(files[i]); | |
445 } | |
446 }; | |
447 function saveChanges() { | |
448 notice('Saving data... please wait'); | |
449 var params = {}; | |
450 params['fileCount'] = files.length; | |
451 var c = 0; | |
452 for (var i=0; i < files.length; i++) { | |
453 var name = files[i].name; | |
454 params['name'+c] = name; | |
455 params['contents'+c] = files[i].editor.getCode(); | |
456 c++; | |
457 } | |
458 $.post("./NamlEditor$Save.jtp", params, | |
459 function(data){ | |
460 if (data == null || data.length == 0) { | |
461 setError(null); | |
462 notice('Data successfully saved, no errors found', 2000, 2000); | |
463 resetModified(); | |
464 } else { | |
465 setError(data); | |
466 notice('Data NOT saved, please fix errors', 5000, 2000); | |
467 } | |
468 } | |
469 ); | |
470 }; | |
471 | |
472 function getOrCreateFile(name) { | |
473 for (var i=0; i<files.length; i++) { | |
474 if (files[i].name == name) | |
475 return files[i]; | |
476 } | |
477 files.push({ | |
478 id: new Date().getTime(), | |
479 name: name, | |
480 contents:'' | |
481 }); | |
482 return files[files.length-1]; | |
483 }; | |
484 function getLineNumber(s, token) { | |
485 var line = 1; | |
486 var pos = s.indexOf(token); | |
487 for (var i=0; i < pos; i++) { | |
488 if (s.charAt(i) == '\n') | |
489 line++; | |
490 } | |
491 return line; | |
492 }; | |
493 function startDefaultTabs() { | |
494 /* Advanced options tab*/ | |
495 var $tabs = $('#tab_advanced_options'); | |
496 $tabs.click(function() { select(advancedOptions); }); | |
497 }; | |
498 function setError(err) { | |
499 var $errorMsg = $('#error-message'); | |
500 $errorMsg.html(err? err : ''); | |
501 var $status = $('#error-status'); | |
502 if (err) { | |
503 var visible = false; | |
504 function errorClick(e) { | |
505 e.stopPropagation(); | |
506 if (visible) { | |
507 $errorMsg.slideUp('fast', function() { $errorMsg.hide() }); | |
508 $status.html('View Error'); | |
509 } else { | |
510 $errorMsg.slideDown('fast'); | |
511 $status.html('Hide Error'); | |
512 } | |
513 visible = !visible; | |
514 }; | |
515 $status.addClass('has-error').removeClass('no-error').html('View Error').click(errorClick).click(); | |
516 $(document).click(function(e) { | |
517 if ($(e.target).attr('id') == 'error-message') | |
518 return; | |
519 var $parents = $(e.target).parents(); | |
520 if ($parents.hasClass('error-message')) | |
521 return; | |
522 visible = true; | |
523 $status.click(); | |
524 }); | |
525 } else { | |
526 $status.addClass('no-error').removeClass('has-error').html('No Errors').unbind('click'); | |
527 $(document).unbind('click'); | |
528 } | |
529 }; | |
530 var error = <%=exception == null? "null" : "\"" + HtmlUtils.javascriptStringEncode(getErrorMessage(exception)) + "\""%>; | |
531 | |
532 function startEditors() { | |
533 notice('Starting editor... please wait'); | |
534 startDefaultTabs(); | |
535 setError(error); | |
536 layout(); | |
537 var selectedFileName = <%=selectedFileName == null? "null" : "\""+selectedFileName+"\""%>; | |
538 var selectedFile = files[0]; | |
539 var macroDef = <%=macroDef == null? "null" : "\""+HtmlUtils.javascriptStringEncode(macroDef)+"\""%>; | |
540 var overridenBody = <%=overridenBody == null? "null" : "\""+HtmlUtils.javascriptStringEncode(overridenBody)+"\""%>; | |
541 var macroLineNumber = 0; | |
542 if (selectedFileName != null) { | |
543 var f = getOrCreateFile(selectedFileName); | |
544 macroLineNumber = getLineNumber(f.contents,macroDef); | |
545 } else if (overridenBody != null) { | |
546 var f = getOrCreateFile('tweaks'); | |
547 selectedFileName = f.name; | |
548 f.contents = Nabble.trim(f.contents); | |
549 f.contents = (f.contents.length > 0? f.contents + '\n\n':'') + overridenBody; | |
550 macroLineNumber = getLineNumber(f.contents,macroDef); | |
551 } else { | |
552 var f = files.length == 0? getOrCreateFile('tweaks') : files[0]; | |
553 selectedFileName = f.name; | |
554 } | |
555 for (var i=0; i < files.length; i++) { | |
556 addFile(files[i]); | |
557 if (files[i].name == selectedFileName) | |
558 selectedFile = files[i]; | |
559 } | |
560 select(selectedFile); | |
561 if (selectedFile && macroLineNumber > 0) { | |
562 function goToLine() { | |
563 var $lines = $('#wrapping_'+selectedFile.id+' div.CodeMirror-line-numbers').children(); | |
564 if (selectedFile.editor.editor && $lines.size() >= macroLineNumber) { | |
565 var c = 1; | |
566 $lines.each(function() { | |
567 var t = $(this).html(); | |
568 if (c == macroLineNumber) { | |
569 $(this).css('font-weight','bold').addClass('highlight'); | |
570 setTimeout(function(){ | |
571 selectedFile.editor.jumpToLine(Number(macroLineNumber)); | |
572 notice('Ready', 2000, 1000); | |
573 }, 150); | |
574 return false; | |
575 } | |
576 if (t != " ") | |
577 c++; | |
578 }); | |
579 } else | |
580 setTimeout(goToLine, 50); | |
581 }; | |
582 goToLine(); | |
583 } else | |
584 notice('Ready', 2000, 1000); | |
585 $(window).resize(layout); | |
586 }; | |
587 | |
588 var started = false; | |
589 $(document).ready(function() { | |
590 if (!started) { | |
591 started = true; | |
592 startEditors(); | |
593 } | |
594 }); | |
595 </script> | |
596 </head> | |
597 <body> | |
598 <div id="notice" class="notice rounded-bottom"></div> | |
599 <% Shared.minHeader(request, response, site.getRootNode(), null, false); %> | |
600 | |
601 <div class="shaded-bg-color" style="padding:.5em"> | |
602 <div id="error-status" class="status-label rounded no-error">No Errors</div> | |
603 <div id="error-message"class="border2 error-message drop-shadow"></div> | |
604 <span class="big-title second-font">NAML Editor</span> | |
605 </div> | |
606 | |
607 <div class="main-content"> | |
608 <table class="vertical-control"> | |
609 <tr> | |
610 <td class="options"> | |
611 <button class="toolbar" onclick="newFile()">Add New File</button> | |
612 <ul id="options-ul" class="vertical-control"> | |
613 </ul> | |
614 <div id="more" class="more"> | |
615 <ul class="vertical-control"> | |
616 <li id="tab_advanced_options" class="weak-color"> | |
617 <img src="/images/tool.png" width="16" height="17" style="vertical-align:-15%"/> | |
618 Advanced Options | |
619 </li> | |
620 <% if (previousUrl != null) { %> | |
621 <li style="cursor:text"> | |
622 <img src="/images/arrowleft.png" style="width:15px;height:15px;vertical-align:-15%;margin-right:1px"/> | |
623 <a href="<%=previousUrl%>">Continue</a> | |
624 </li> | |
625 <% } %> | |
626 </ul> | |
627 <button class="toolbar action-button" onclick="saveChanges()" style="width:100%;padding:.4em 0">Save Changes</button> | |
628 </div> | |
629 </td> | |
630 <td id="details" class="details dark-bg-color"> | |
631 <div id="no-selection" class="no-bg-color" style="display:none;padding:.2em 1em"> | |
632 <div class="big-title second-font weak-color">No Files</div> | |
633 <div class="weak-color">Use the button on the left to create a new file.</div> | |
634 </div> | |
635 <div id="div_advanced_options" class="no-bg-color default" style="display:none;padding:.2em 1em"> | |
636 <div id="modified-warning" class="info-message invisible" style="padding: .2em .5em .5em"> | |
637 <div class="big-title important">Warning -- You have unsaved changes!</div> | |
638 It is highly recommended that you save your changes before using the options below. | |
639 The download option will NOT see your modified files and the upload option will override your last changes. Click on the | |
640 "Save Changes" button first if you don't want to lose your modified files. | |
641 </div> | |
642 <div class="big-title second-font">Download Changes</div> | |
643 <div class="advanced-option-body"> | |
644 Download a ZIP file with your custom NAML code and save a copy on your computer. | |
645 <div style="padding:.3em 0"> | |
646 <a href="<%=NamlDownload.getFilePath(site)%>">Download ZIP file</a> | |
647 </div> | |
648 </div> | |
649 | |
650 <div class="big-title second-font">Upload Changes</div> | |
651 <div class="advanced-option-body"> | |
652 Import a ZIP file with custom NAML code.<br/> | |
653 You may consider downloading a backup before uploading a new file.<br/> | |
654 <iframe id="upload-frame" src="./NamlEditor$UploadForm.jtp" frameborder="0" width="100%" scrolling="0"></iframe> | |
655 </div> | |
656 </div> | |
657 </td> | |
658 </tr> | |
659 </table> | |
660 </div> | |
661 | |
662 <% Shared.noFooter(request,response); %> | |
663 <% Shared.analytics(request,response); %> | |
664 </body> | |
665 </html> | |
666 <% | |
667 } | |
668 | |
669 public static void save(HttpServletRequest request, HttpServletResponse response) | |
670 throws ServletException, IOException | |
671 { | |
672 Site site = Jtp.getSite(request); | |
673 int fileCount = Integer.valueOf(request.getParameter("fileCount")); | |
674 Map<String, String> tweaks = new HashMap<String, String>(); | |
675 for (int i = 0; i < fileCount; i++) { | |
676 String name = request.getParameter("name"+i); | |
677 String contents = request.getParameter("contents"+i); | |
678 tweaks.put(name, contents); | |
679 } | |
680 Map<String, String> previousTweaks = site.getCustomTweaks(); | |
681 site.setCustomTweaks(tweaks); | |
682 try { | |
683 site.getProgram().getTemplate("do_nothing"); | |
684 } catch (CompileException e) { | |
685 response.getWriter().print(getErrorMessage(e)); | |
686 // reverting changes | |
687 site.setCustomTweaks(previousTweaks); | |
688 } | |
689 } | |
690 | |
691 public static void uploadForm(HttpServletRequest request, HttpServletResponse response) | |
692 throws ServletException, IOException | |
693 { | |
694 PrintWriter out = response.getWriter(); | |
695 %> | |
696 <html> | |
697 <head> | |
698 <% Shared.head(request, response, false); %> | |
699 </head> | |
700 <body style="margin-left:0"> | |
701 <form action="./NamlEditor$Upload.jtp" method="POST" enctype="multipart/form-data"> | |
702 <input name="file" type="file" size="20" /> | |
703 <div style="padding:.5em 0"> | |
704 <input type="radio" name="type" id="override" value="override" checked="true"/> <label for="override">Override my changes</label><br/> | |
705 <input type="radio" name="type" id="append" value="append"/> <label for="append">Merge with my changes</label><br/> | |
706 </div> | |
707 <button type="submit" class="toolbar second-font" style="padding:.25em">Upload ZIP File</button> | |
708 </form> | |
709 </body> | |
710 </html> | |
711 <% | |
712 } | |
713 | |
714 private static final int SIZE_LIMIT = 1024 * 1024 * 2; // 2 Mb | |
715 private static final CharsetEncoder CHARSET_ENCODER = Charset.forName("UTF-8").newEncoder(); | |
716 | |
717 public static void upload(HttpServletRequest request, HttpServletResponse response) | |
718 throws ServletException, IOException | |
719 { | |
720 final Map<String, FileItem> map; | |
721 try { | |
722 map = Jtp.getFileItems(request); | |
723 } catch (FileUploadException e) { | |
724 resultPage("Upload failed: " + e.getMessage(), request, response); | |
725 return; | |
726 } | |
727 FileItem fi = map.get("file"); | |
728 if (fi == null) { | |
729 resultPage("Image upload failed, please try again.", request, response); | |
730 return; | |
731 } else if (fi.getSize() > SIZE_LIMIT) { | |
732 resultPage("The file you uploaded is too big. Please upload a smaller image (less than 2Mb).", request, response); | |
733 return; | |
734 } | |
735 Site site = Jtp.getSite(request); | |
736 Map<String,String> tweaks = new HashMap<String, String>(site.getCustomTweaks()); | |
737 | |
738 String type = map.get("type").getString(); | |
739 boolean isOverride = "override".equals(type); | |
740 if (isOverride) | |
741 tweaks.clear(); | |
742 | |
743 InputStream in = fi.getInputStream(); | |
744 ZipInputStream zis = new ZipInputStream(in); | |
745 ZipEntry entry; | |
746 int i = 0; | |
747 while ((entry = zis.getNextEntry()) != null) { | |
748 String name = entry.getName(); | |
749 if (name.endsWith(".naml")) { | |
750 int size; | |
751 byte[] buffer = new byte[2048]; | |
752 ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
753 while ((size = zis.read(buffer, 0, buffer.length)) != -1) { | |
754 baos.write(buffer, 0, size); | |
755 } | |
756 if (CHARSET_ENCODER.canEncode(baos.toString())) { | |
757 String contents = baos.toString("UTF-8"); | |
758 name = name.replaceAll("\\.naml", ""); // remove ".naml" ext | |
759 if (tweaks.containsKey(name)) { | |
760 String original = tweaks.get(name); | |
761 tweaks.put(name, original + "\n\n----------- Merged -----------\n\n" + contents); | |
762 } else | |
763 tweaks.put(name, contents); | |
764 baos.flush(); | |
765 baos.close(); | |
766 i++; | |
767 } | |
768 } | |
769 } | |
770 if (i == 0) { | |
771 resultPage("Invalid ZIP file.", request, response); | |
772 return; | |
773 } | |
774 // Saves tweaks and recompiles XML | |
775 site.setCustomTweaks(tweaks); | |
776 site.getTemplate("do_nothing"); | |
777 // Reloads the page (if there is any exception, it will be loaded and displayed) | |
778 resultPage(null, request, response); | |
779 } | |
780 | |
781 private static void resultPage(String message, HttpServletRequest request, HttpServletResponse response) | |
782 throws IOException | |
783 { | |
784 PrintWriter out = response.getWriter(); | |
785 %> | |
786 <html> | |
787 <head> | |
788 <% Shared.head(request, response, false); %> | |
789 </head> | |
790 <body style="margin-left:0"> | |
791 <% if (message == null) { %> | |
792 <script type="text/javascript"> | |
793 parent.location = './NamlEditor.jtp'; | |
794 </script> | |
795 <% } else { %> | |
796 Error: <span style="color:red"><%=message%></span> | |
797 <br/><br/> | |
798 <a href="./NamlEditor$UploadForm.jtp">Try again</a> | |
799 <% } %> | |
800 </body> | |
801 </html> | |
802 <% | |
803 } | |
804 | |
805 private static String getErrorMessage(Exception exception) { | |
806 StringBuilder errorMessage = new StringBuilder(); | |
807 if (exception != null) { | |
808 errorMessage | |
809 .append("<div class=\"important big-title second-font\">Error</div>") | |
810 .append(exception.getMessage().replaceAll("\n","<br/>")) | |
811 .append("<div class=\"bold\" style=\"padding:.5em 0 .3em\">Full Stack Trace</div>"); | |
812 StackTraceElement[] stack = exception.getStackTrace(); | |
813 for (StackTraceElement e : stack) | |
814 errorMessage.append(e).append("<br/>"); | |
815 } | |
816 return errorMessage.toString(); | |
817 } | |
818 | |
819 public static class Save extends HttpServlet { | |
820 | |
821 protected void service(HttpServletRequest request, HttpServletResponse response) | |
822 throws ServletException, IOException | |
823 { | |
824 User user = Jtp.getUser(request, response); | |
825 boolean isSiteAdmin = user != null && Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP); | |
826 boolean isSysAdmin = user != null && Permissions.isSysAdmin(user); | |
827 if ((isSiteAdmin || isSysAdmin) && "POST".equals(request.getMethod())) { | |
828 save(request, response); | |
829 } else | |
830 response.getWriter().print("Please log in with your administrator account. Your may have logged out or your session has expired."); | |
831 } | |
832 } | |
833 | |
834 public static class UploadForm extends HttpServlet { | |
835 | |
836 protected void service(HttpServletRequest request, HttpServletResponse response) | |
837 throws ServletException, IOException | |
838 { | |
839 uploadForm(request, response); | |
840 } | |
841 } | |
842 | |
843 public static class Upload extends HttpServlet { | |
844 | |
845 protected void service(HttpServletRequest request, HttpServletResponse response) | |
846 throws ServletException, IOException | |
847 { | |
848 User user = Jtp.getUser(request, response); | |
849 if (user == null) { | |
850 resultPage("You are not logged in as an administrator.", request, response); | |
851 return; | |
852 } | |
853 boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP); | |
854 boolean isSysAdmin = Permissions.isSysAdmin(user); | |
855 if (!isSiteAdmin && !isSysAdmin) { | |
856 resultPage("You are not logged in as an administrator.", request, response); | |
857 return; | |
858 } | |
859 | |
860 upload(request, response); | |
861 } | |
862 } | |
863 | |
864 private static void simpleLoginPage(String message, HttpServletRequest request, HttpServletResponse response) | |
865 throws ServletException, IOException | |
866 { | |
867 PrintWriter out = response.getWriter(); | |
868 %> | |
869 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |
870 <html> | |
871 <head> | |
872 <% Shared.loadJavascript(request, out); %> | |
873 <title>Naml Editor | Login</title> | |
874 </head> | |
875 <body style="text-align:center;font-family: Verdana,sans-serif;font-size:.84em;padding-top:2em"> | |
876 <img src="/images/naml.png"/> | |
877 <p><b><%=message%></b></p> | |
878 <form action="./NamlEditor$SimpleLogin.jtp" method="post"> | |
879 <table style="margin:0 auto;font:inherit"> | |
880 <tr> | |
881 <td style="text-align:right">Email</td> | |
882 <td><input type="text" name="email"/></td> | |
883 </tr> | |
884 <tr> | |
885 <td style="text-align:right">Password</td> | |
886 <td><input type="password" name="password"/></td> | |
887 </tr> | |
888 <tr> | |
889 <td colspan="2" style="padding: .5em 0 0;text-align:center"> | |
890 <input type="submit" value="Login" style="font-weight:bold;padding:.3em .4em"/> | |
891 or <a href="/">Cancel</a> | |
892 </td> | |
893 </tr> | |
894 </table> | |
895 </form> | |
896 <div style="margin:2em 15%;text-align:center;background:#eee;padding:.3em 0"> | |
897 Powered by <a href="<%=Jtp.homePage()%>">Nabble</a> | |
898 </div> | |
899 </body> | |
900 </html> | |
901 <% | |
902 } | |
903 | |
904 public static class SimpleLogin extends HttpServlet { | |
905 | |
906 protected void service(HttpServletRequest request, HttpServletResponse response) | |
907 throws ServletException, IOException | |
908 { | |
909 Site site = Jtp.getSite(request); | |
910 String email = request.getParameter("email"); | |
911 String password = request.getParameter("password"); | |
912 User user = site.getUserFromEmail(email); | |
913 if (user != null && user.isRegistered() && user.checkPassword(password) && "post".equalsIgnoreCase(request.getMethod())) { | |
914 Jtp.doLogin(request,response,user,true); | |
915 DailyNumber.logins.inc(); | |
916 | |
917 Shared.javascriptRedirect(request,response, "./NamlEditor.jtp"); | |
918 } else { | |
919 simpleLoginPage("<span style=\"color:#C00\">Incorrect login!</span>", request, response); | |
920 } | |
921 } | |
922 } | |
923 | |
924 public static class ViewFile extends HttpServlet { | |
925 | |
926 protected void service(HttpServletRequest request, HttpServletResponse response) | |
927 throws ServletException, IOException | |
928 { | |
929 JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName); | |
930 jtpContext.setEtag(request,response); | |
931 | |
932 Site site = Jtp.getSite(request); | |
933 String file = request.getParameter("file"); | |
934 PrintWriter out = response.getWriter(); | |
935 List<Source> sources = site.getProgram().getSources(); | |
936 for (Source s : sources) { | |
937 if (s.id.equals(file)) { | |
938 file = file.replaceFirst("^.+:", ""); | |
939 file = file.endsWith(".naml")? file : file + ".naml"; | |
940 String code = HtmlUtils.htmlEncode(s.content); | |
941 %> | |
942 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |
943 <html> | |
944 <head> | |
945 <title><%=file%> | Naml File</title> | |
946 <% Shared.head(request, response, false); %> | |
947 </head> | |
948 <body> | |
949 <% Shared.minHeader(request, response, site.getRootNode(), null, false); %> | |
950 <h1><%=file%></h1> | |
951 <pre><%=code%></pre> | |
952 <% Shared.footer(request, response); %> | |
953 </body> | |
954 </html> | |
955 <% | |
956 return; | |
957 } | |
958 } | |
959 response.sendError(HttpServletResponse.SC_NOT_FOUND, "Source file not found."); | |
960 } | |
961 } | |
962 } | |
963 %> |