61
61
/* Mapping SVN status to icons, just the file's basename */
63
"unversioned": "unversioned.png",
64
64
"normal": "normal.png",
66
"missing": "missing.png",
67
"deleted": "deleted.png",
68
65
"modified": "modified.png",
69
"revision": "revision.png"
72
/* Mapping SVN status to "nice" strings */
74
"unversioned": "Temporary file",
75
"normal": "Permanent file",
76
"added": "Temporary file (scheduled to be added)",
77
"missing": "Permanent file (missing)",
78
"deleted": "Permanent file (scheduled for deletion)",
79
"replaced": "Permanent file (replaced)",
80
"modified": "Permanent file (modified)",
81
"merged": "Permanent file (merged)",
82
"conflicted": "Permanent file (conflicted)",
83
"revision": "Past Permanent file (revision)"
86
default_svn_icon = null;
87
default_svn_nice = "Unknown status";
68
default_svn_icon = "normal.png";
89
70
svn_icons_path = "media/images/svn";
91
published_icon = "media/images/interface/published.png";
93
72
/* List of MIME types considered "executable" by the system.
94
73
* Executable files offer a "run" link, implying that the "serve"
95
74
* application can interpret them.
102
/* Global variables */
104
/** The listing object returned by the server as JSON */
109
/** Filenames of all files selected
110
* (Only used by dir listings, but still needs to be [] for files, so that
111
* update_actions knows that nothing is selected).
115
upload_callback_count = 0; /* See upload_callback */
117
80
/** Calls the server using Ajax, performing an action on the server side.
118
81
* Receives the response from the server and performs a refresh of the page
119
82
* contents, updating it to display the returned data (such as a directory
236
/* This will always return a listing, whether it is a dir or a file.
238
var listing = response.responseText;
239
/* The listing SHOULD be valid JSON text. Parse it into an object. */
242
listing = JSON.parse(listing);
243
file_listing = listing.listing; /* Global */
247
handle_error("The server returned an invalid directory listing");
250
/* Get "." out, it's special */
251
current_file = file_listing["."]; /* Global */
252
delete file_listing["."];
254
165
/* Check if this is a directory listing or file contents */
255
var isdir = response.getResponseHeader("X-IVLE-Return") == "Dir";
166
if (response.getResponseHeader("X-IVLE-Return") == "Dir")
168
var listing = response.responseText;
169
/* The listing SHOULD be valid JSON text. Parse it into an object. */
172
listing = JSON.parse(listing);
176
handle_error("The server returned an invalid directory listing");
258
179
handle_dir_listing(path, listing);
262
/* Need to make a 2nd ajax call, this time get the actual file
264
callback = function(response)
266
/* Read the response and set up the page accordingly */
267
handle_contents_response(path, response);
269
/* Call the server and request the listing. */
271
args = shallow_clone_object(url_args);
183
/* Treat this as an ordinary file. Get the file type. */
184
var content_type = response.getResponseHeader("Content-Type");
186
if (content_type in type_handlers)
187
handler_type = type_handlers[content_type];
274
/* This time, get the contents of the file, not its metadata */
275
args['return'] = "contents";
276
ajax_call(callback, service_app, path, args, "GET");
278
update_actions(isdir);
281
function handle_contents_response(path, response)
283
/* Treat this as an ordinary file. Get the file type. */
284
var content_type = response.getResponseHeader("Content-Type");
285
var handler_type = get_handler_type(content_type);
286
would_be_handler_type = handler_type;
287
/* handler_type should now be set to either
288
* "text", "image", "audio" or "binary". */
289
switch (handler_type)
292
handle_text(path, response.responseText,
293
would_be_handler_type);
296
/* TODO: Custom image handler */
297
handle_binary(path, response.responseText);
300
/* TODO: Custom audio handler */
301
handle_binary(path, response.responseText);
309
/* Called when a form upload comes back (from an iframe).
310
* Refreshes the page.
312
function upload_callback()
314
/* This has a pretty nasty hack, which happens to work.
315
* upload_callback is set as the "onload" callback for the iframe which
316
* receives the response from the server for uploading a file.
317
* This means it gets called twice. Once when initialising the iframe, and
318
* a second time when the actual response comes back.
319
* All we want to do is call navigate to refresh the page. But we CAN'T do
320
* that on the first load or it will just go into an infinite cycle of
321
* refreshing. We need to refresh the page ONLY on the second refresh.
322
* upload_callback_count is reset to 0 just before the iframe is created.
324
upload_callback_count++;
325
if (upload_callback_count >= 2)
189
{ /* Based on the first part of the MIME type */
190
handler_type = content_type.split('/')[0];
191
if (handler_type != "text" && handler_type != "image" &&
192
handler_type != "audio")
193
handler_type = "binary";
195
/* If we're in "edit mode", always treat this file as text */
196
would_be_handler_type = handler_type;
197
if (editmode) handler_type = "text";
198
/* handler_type should now be set to either
199
* "text", "image", "audio" or "binary". */
200
switch (handler_type)
203
handle_text(path, response.responseText, would_be_handler_type);
206
/* TODO: Custom image handler */
207
handle_binary(path, response.responseText);
210
/* TODO: Custom audio handler */
211
handle_binary(path, response.responseText);
329
220
/** Deletes all "dynamic" content on the page.
391
350
filename = svn_icons[svnstatus];
393
352
filename = default_svn_icon;
394
if (filename == null) return null;
395
353
return make_path(path_join(svn_icons_path, filename));
398
/** Given an svnstatus, returns the "nice" string.
356
/** Presents the text editor.
400
function svnstatus_to_string(svnstatus)
358
function handle_text(path, text, handler_type)
402
if (svnstatus in svn_nice)
403
return svn_nice[svnstatus];
405
return default_svn_nice;
360
/* Create a textarea with the text in it
361
* (The makings of a primitive editor).
365
var files = document.getElementById("filesbody");
366
var div = document.createElement("div");
367
files.appendChild(div);
368
div.setAttribute("class", "padding");
369
/* First, print a warning message if this is not actually a text file.
371
if (handler_type != "text")
373
var warn = dom_make_text_elem("p",
374
"Warning: You are editing a binary " +
375
"file, which explains any strange characters you may see. If " +
376
"you save this file, you could corrupt it.");
377
div.appendChild(warn);
379
var txt_elem = dom_make_text_elem("textarea",
381
div.appendChild(txt_elem);
382
txt_elem.setAttribute("id", "editbox");
383
/* TODO: Make CSS height: 100% work */
384
txt_elem.setAttribute("rows", "20");
408
387
/** Displays a download link to the binary file.
410
389
function handle_binary(path)
412
392
var files = document.getElementById("filesbody");
413
393
var div = document.createElement("div");
414
394
files.appendChild(div);
415
395
div.setAttribute("class", "padding");
416
var download_link = app_path(download_app, path);
396
var download_link = make_path(path_join(download_app, path));
417
397
var par1 = dom_make_text_elem("p",
418
398
"The file " + path + " is a binary file. To download this file, " +
419
399
"click the following link:");
423
403
div.appendChild(par2);
426
function update_actions()
429
var numsel = selected_files.length;
434
/* Display information about the current directory instead */
435
filename = path_basename(current_path);
438
else if (numsel == 1)
440
filename = selected_files[0];
441
file = file_listing[filename];
444
/* Update each action node in the topbar.
445
* This includes enabling/disabling actions as appropriate, and
446
* setting href/onclick attributes. */
450
/* Available if exactly one file is selected */
451
var open = document.getElementById("act_open");
454
open.setAttribute("class", "choice");
456
open.setAttribute("title",
457
"Navigate to this directory in the file browser");
459
open.setAttribute("title",
460
"Edit or view this file");
461
open.setAttribute("href", app_path(this_app, current_path, filename));
465
open.setAttribute("class", "disabled");
466
open.removeAttribute("title");
467
open.removeAttribute("href");
471
/* Available if exactly one file is selected,
472
* and only if this is a file, not a directory */
473
var serve = document.getElementById("act_serve");
474
if (numsel == 1 && !file.isdir)
476
serve.setAttribute("class", "choice");
477
serve.setAttribute("href",
478
app_path(serve_app, current_path, filename));
482
serve.setAttribute("class", "disabled");
483
serve.removeAttribute("href");
487
/* Available if exactly one file is selected,
488
* and it is a Python file.
490
var run = document.getElementById("act_run");
492
if (numsel == 0 && !file.isdir && file.type == "text/x-python")
494
// In the edit window
495
run.setAttribute("class", "choice");
496
localpath = app_path('home',current_path);
497
run.setAttribute("onclick", "runfile('" + localpath + "')");
499
else if (numsel == 1 && !file.isdir && file.type == "text/x-python")
501
// In the browser window
502
run.setAttribute("class", "choice");
503
localpath = app_path('home',current_path,filename);
504
run.setAttribute("onclick", "runfile('" + localpath + "')");
508
run.setAttribute("class", "disabled");
509
run.removeAttribute("onclick");
514
* If 0 files selected, download the current file or directory as a ZIP.
515
* If 1 directory selected, download it as a ZIP.
516
* If 1 non-directory selected, download it.
517
* If >1 files selected, download them all as a ZIP.
519
var download = document.getElementById("act_download");
524
download.setAttribute("href",
525
app_path(download_app, current_path));
527
download.setAttribute("title",
528
"Download the current directory as a ZIP file");
530
download.setAttribute("title",
531
"Download the current file");
535
download.setAttribute("href",
536
app_path(download_app, current_path, filename));
538
download.setAttribute("title",
539
"Download the selected directory as a ZIP file");
541
download.setAttribute("title",
542
"Download the selected file");
547
/* Make a query string with all the files to download */
548
var dlpath = urlencode_path(app_path(download_app, current_path)) + "?";
549
for (var i=0; i<numsel; i++)
550
dlpath += "path=" + encodeURIComponent(selected_files[i]) + "&";
551
dlpath = dlpath.substr(0, dlpath.length-1);
552
download.setAttribute("href", dlpath);
553
download.setAttribute("title",
554
"Download the selected files as a ZIP file");
557
/* Refresh - No changes required */
559
/* Publish and Submit */
560
/* If this directory is under subversion and selected/unselected file is a
562
var publish = document.getElementById("act_publish");
563
var submit = document.getElementById("act_submit");
564
if (numsel <= 1 && file.isdir)
566
/* TODO: Work out of file is svn'd */
567
/* TODO: If this dir is already published, call it "Unpublish" */
568
publish.setAttribute("class", "choice");
569
publish.removeAttribute("disabled");
570
submit.setAttribute("class", "choice");
571
submit.removeAttribute("disabled");
575
publish.setAttribute("class", "disabled");
576
publish.setAttribute("disabled", "disabled");
577
submit.setAttribute("class", "disabled");
578
submit.setAttribute("disabled", "disabled");
582
/* If exactly 1 non-directory file is selected/opened, and its parent
583
* directory is published.
585
var share = document.getElementById("act_share");
586
if (numsel <= 1 && !file.isdir)
588
/* TODO: Work out if parent dir is published */
589
share.setAttribute("class", "choice");
590
share.removeAttribute("disabled");
594
share.setAttribute("class", "disabled");
595
share.setAttribute("disabled", "disabled");
599
/* If exactly 1 file is selected */
600
var rename = document.getElementById("act_rename");
603
rename.setAttribute("class", "choice");
604
rename.removeAttribute("disabled");
608
rename.setAttribute("class", "disabled");
609
rename.setAttribute("disabled", "disabled");
612
/* Delete, cut, copy */
613
/* If >= 1 file is selected */
614
var act_delete = document.getElementById("act_delete");
615
var cut = document.getElementById("act_cut");
616
var copy = document.getElementById("act_copy");
619
act_delete.setAttribute("class", "choice");
620
act_delete.removeAttribute("disabled");
621
cut.setAttribute("class", "choice");
622
cut.removeAttribute("disabled");
623
copy.setAttribute("class", "choice");
624
copy.removeAttribute("disabled");
628
act_delete.setAttribute("class", "disabled");
629
act_delete.setAttribute("disabled", "disabled");
630
cut.setAttribute("class", "disabled");
631
cut.setAttribute("disabled", "disabled");
632
copy.setAttribute("class", "disabled");
633
copy.setAttribute("disabled", "disabled");
636
/* Paste, new file, new directory, upload */
637
/* Always enabled (assuming this is a directory) */
639
/* Subversion actions */
640
/* TODO: Work out when these are appropriate */
641
var svnadd = document.getElementById("act_svnadd");
642
var svnrevert = document.getElementById("act_svnrevert");
643
var svncommit = document.getElementById("act_svncommit");
646
svnadd.setAttribute("class", "choice");
647
svnadd.removeAttribute("disabled");
648
svnrevert.setAttribute("class", "choice");
649
svnrevert.removeAttribute("disabled");
650
svncommit.setAttribute("class", "choice");
651
svncommit.removeAttribute("disabled");
653
var svncheckout = document.getElementById("act_svncheckout");
654
/* current_path == username: We are at the top level */
655
if (current_path == username)
657
svncheckout.setAttribute("class", "choice");
658
svncheckout.removeAttribute("disabled");
662
svncheckout.setAttribute("class", "disabled");
663
svncheckout.setAttribute("disabled", "disabled");
669
/** Event handler for when an item of the "More actions..." dropdown box is
670
* selected. Performs the selected action. */
671
function handle_moreactions()
673
var moreactions = document.getElementById("moreactions");
674
if (moreactions.value == "top")
676
var selectedaction = moreactions.value;
677
/* Reset to "More actions..." */
678
moreactions.selectedIndex = 0;
680
/* If 0 files selected, filename is the name of the current dir.
681
* If 1 file selected, filename is that file.
683
if (selected_files.length == 0)
684
filename = path_basename(current_path);
685
else if (selected_files.length == 1)
686
filename = selected_files[0];
690
/* Now handle the selected action */
691
switch(selectedaction)
694
action_publish(selected_files);
697
action_unpublish(selected_files);
701
alert("Not yet implemented: Sharing files");
705
alert("Not yet implemented: Submit");
708
action_rename(filename);
711
action_remove(selected_files);
714
action_copy(selected_files);
717
action_cut(selected_files);
729
show_uploadpanel(true);
732
action_add(selected_files);
735
action_revert(selected_files);
738
action_commit(selected_files);
746
/** User clicks "Run" button.
747
* Do an Ajax call and print the test output.
749
function runfile(localpath)
751
/* Dump the entire file to the console */
752
var callback = function()
754
console_enter_line("execfile('" + localpath + "')", "block");
756
start_server(callback)
760
406
/** Called when the page loads initially.
762
408
window.onload = function()