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 != "&nbsp;")
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 %>