~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to www/media/browser/browser.js

  • Committer: mattgiuca
  • Date: 2008-02-19 08:26:11 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:509
common.db: Rewrote user_authenticate to return 3 values (True, false, None)
    Now returns False if the password did not match, None if the password
    field is NULL (None implying a soft failure, with the possibility of
    validating against LDAP or something else).

auth.authenticate: Rewrote this module with a new plugin interface
    (as discussed with Tom Conway). Allows successive modules to try to
    authenticate the user.
    Changed the authenticate function interface: Now raises an AuthError
    when auth fails, instead of returning None.

dispatch.login: Handle new auth interface (exception catch).
    Auth is now able to provide an error message, in the exception.
    The exception message is displayed as an error to the user.

Show diffs side-by-side

added added

removed removed

Lines of Context:
55
55
default_type_icon = "txt.png";
56
56
 
57
57
/* Relative to IVLE root */
58
 
type_icons_path = "+media/ivle.webapp.core/images/mime";
59
 
type_icons_path_large = "+media/ivle.webapp.core/images/mime/large";
 
58
type_icons_path = "media/images/mime";
 
59
type_icons_path_large = "media/images/mime/large";
60
60
 
61
61
/* Mapping SVN status to icons, just the file's basename */
62
62
svn_icons = {
63
 
    "unversioned": "unversioned.png",
 
63
    "unversioned": null,
64
64
    "normal": "normal.png",
65
65
    "added": "added.png",
66
66
    "missing": "missing.png",
67
67
    "deleted": "deleted.png",
68
 
    "modified": "modified.png",
69
 
    "conflicted": "conflicted.png",
70
 
    "revision": "revision.png"
 
68
    "modified": "modified.png"
71
69
};
72
70
 
73
71
/* Mapping SVN status to "nice" strings */
80
78
    "replaced": "Permanent file (replaced)",
81
79
    "modified": "Permanent file (modified)",
82
80
    "merged": "Permanent file (merged)",
83
 
    "conflicted": "Permanent file (conflicted)",
84
 
    "revision": "Past Permanent file (revision)"
 
81
    "conflicted": "Permanent file (conflicted)"
85
82
};
86
83
 
87
84
default_svn_icon = null;
88
85
default_svn_nice = "Unknown status";
89
86
 
90
 
svn_icons_path = "+media/ivle.webapp.core/images/svn";
 
87
svn_icons_path = "media/images/svn";
91
88
 
92
 
published_icon = "+media/ivle.webapp.core/images/interface/published.png";
 
89
published_icon = "media/images/interface/published.png";
93
90
 
94
91
/* List of MIME types considered "executable" by the system.
95
92
 * Executable files offer a "run" link, implying that the "serve"
102
99
 
103
100
/* Global variables */
104
101
 
105
 
/** The listing object returned by the server as JSON */
106
 
file_listing = null;
107
 
current_file = null;
108
 
current_revision = null;
109
102
current_path = "";
110
103
 
111
 
/** Filenames of all files selected
112
 
 * (Only used by dir listings, but still needs to be [] for files, so that
113
 
 * update_actions knows that nothing is selected).
114
 
 */
115
 
selected_files = [];
116
 
 
117
 
upload_callback_count = 0;      /* See upload_callback */
118
 
 
119
104
/** Calls the server using Ajax, performing an action on the server side.
120
105
 * Receives the response from the server and performs a refresh of the page
121
106
 * contents, updating it to display the returned data (such as a directory
134
119
 *      May be "application/x-www-form-urlencoded" or "multipart/form-data".
135
120
 *      Defaults to "application/x-www-form-urlencoded".
136
121
 *      "multipart/form-data" is recommended for large uploads.
137
 
 * \param callback, optional.
138
 
 *      A callback function for after the action has been handled.
139
122
 */
140
 
function do_action(action, path, args, content_type, callback)
 
123
function do_action(action, path, args, content_type, ignore_response)
141
124
{
142
125
    args.action = action;
143
 
    /* Callback action, when the server returns */
144
 
    var callback_inner = function(response)
145
 
        {
146
 
            /* Check for action errors reported by the server, and report them
147
 
             * to the user */
148
 
            var error = response.getResponseHeader("X-IVLE-Action-Error");
149
 
            if (error != null && error != "")
150
 
                /* Note: This header (in particular) comes URI-encoded, to
151
 
                 * allow multi-line error messages. Decode */
152
 
                alert("Error: " + decodeURIComponent(error.toString()) + ".");
153
 
            /* Now read the response and set up the page accordingly */
154
 
            if (callback != null)
155
 
                callback(path, response);
156
 
        }
157
126
    /* Call the server and perform the action. This mutates the server. */
158
 
    ajax_call(callback_inner, service_app, path, args, "POST", content_type);
 
127
    response = ajax_call(service_app, path, args, "POST", content_type);
 
128
    /* Check for action errors reported by the server, and report them to the
 
129
     * user */
 
130
    error = response.getResponseHeader("X-IVLE-Action-Error");
 
131
    if (error != null)
 
132
        alert("Error: " + error.toString() + ".");
 
133
    /* Now read the response and set up the page accordingly */
 
134
    if (ignore_response != true)
 
135
        handle_response(path, response);
159
136
}
160
137
 
161
138
/** Calls the server using Ajax, requesting a directory listing. This should
165
142
 * Called "navigate", can also be used for a simple refresh.
166
143
 * Always makes a GET request.
167
144
 * No return value.
168
 
 */
169
 
function navigate(path)
170
 
{
171
 
    callback = function(response)
172
 
        {
173
 
            /* Read the response and set up the page accordingly */
174
 
            handle_response(path, response, false, url.args);
175
 
        }
176
 
    /* Get any query strings */
177
 
    url = parse_url(window.location.href);
178
 
    
179
 
    /* Call the server and request the listing. */
180
 
    ajax_call(callback, service_app, path, url.args, "GET");
181
 
}
182
 
 
183
 
/* Refreshes the current view.
184
 
 * Calls navigate on the current path.
185
 
 */
186
 
function refresh()
187
 
{
188
 
    if (maybe_save('All changes since the last save will be lost!'))
189
 
        navigate(current_path);
 
145
 * \param editmode Optional boolean. If true, then the user navigated here
 
146
 * with an "edit" URL so we should favour using the editor.
 
147
 */
 
148
function navigate(path, editmode)
 
149
{
 
150
    /* Call the server and request the listing. This mutates the server. */
 
151
    response = ajax_call(service_app, path, null, "GET");
 
152
    /* Now read the response and set up the page accordingly */
 
153
    handle_response(path, response, editmode);
190
154
}
191
155
 
192
156
/** Determines the "handler type" from a MIME type.
218
182
 * things) be used to update the URL in the location bar.
219
183
 * \param response XMLHttpRequest object returned by the server. Should
220
184
 * contain all the response data.
221
 
 * \param is_action Boolean. True if this is the response to an action, false
222
 
 * if this is the response to a simple listing. This is used in handling the
223
 
 * error.
224
 
 * \param url_args Arguments dict, for the arguments passed to the URL
225
 
 * in the browser's address bar (will be forwarded along).
 
185
 * \param editmode Optional boolean. If true, then the user navigated here
 
186
 * with an "edit" URL so we should favour using the editor.
226
187
 */
227
 
function handle_response(path, response, is_action, url_args)
 
188
function handle_response(path, response, editmode)
228
189
{
229
190
    /* TODO: Set location bar to "path" */
230
191
    current_path = path;
231
192
 
232
193
    /* Clear away the existing page contents */
233
194
    clearpage();
 
195
    /* Display the path at the top, for navigation */
 
196
    presentpath(path);
234
197
 
235
198
    /* Check the status, and if not 200, read the error and handle this as an
236
199
     * error. */
243
206
        return;
244
207
    }
245
208
 
246
 
    var subjects = null;
247
 
    var top_level_dir = path==username;
248
 
    if (top_level_dir)
249
 
    {
250
 
        var req = ajax_call(null, "userservice", "get_enrolments", null, "GET")
251
 
        subjects = decode_response(req);
252
 
    }
253
 
 
254
 
 
255
 
    /* This will always return a listing, whether it is a dir or a file.
256
 
     */
257
 
    var listing = response.responseText;
258
 
    /* The listing SHOULD be valid JSON text. Parse it into an object. */
259
 
    try
260
 
    {
261
 
        listing = JSON.parse(listing);
262
 
        file_listing = listing.listing;     /* Global */
263
 
    }
264
 
    catch (e)
265
 
    {
266
 
        if (is_action)
267
 
        {
268
 
            var err = document.createElement("div");
269
 
            var p = dom_make_text_elem("p", "Error: "
270
 
                    + "There was an unexpected server error processing "
271
 
                    + "the selected command.");
272
 
            err.appendChild(p);
273
 
            p = dom_make_text_elem("p", "If the problem persists, please "
274
 
                    + "contact the system administrator.")
275
 
            err.appendChild(p);
276
 
            p = document.createElement("p");
277
 
            var refresh = document.createElement("input");
278
 
            refresh.setAttribute("type", "button");
279
 
            refresh.setAttribute("value", "Back to file view");
280
 
            refresh.setAttribute("onclick", "refresh()");
281
 
            p.appendChild(refresh);
282
 
            err.appendChild(p);
283
 
            handle_error(err);
284
 
        }
285
 
        else
286
 
        {
287
 
            var err = document.createElement("div");
288
 
            var p = dom_make_text_elem("p", "Error: "
289
 
                    + "There was an unexpected server error retrieving "
290
 
                    + "the requested file or directory.");
291
 
            err.appendChild(p);
292
 
            p = dom_make_text_elem("p", "If the problem persists, please "
293
 
                    + "contact the system administrator.")
294
 
            err.appendChild(p);
295
 
            handle_error(err);
296
 
        }
297
 
        return;
298
 
    }
299
 
    /* Get "." out, it's special */
300
 
    current_file = file_listing["."];     /* Global */
301
 
    delete file_listing["."];
302
 
 
303
 
    if ('revision' in listing)
304
 
    {
305
 
        current_revision = listing.revision;
306
 
    }
307
 
 
308
209
    /* Check if this is a directory listing or file contents */
309
210
    var isdir = response.getResponseHeader("X-IVLE-Return") == "Dir";
310
 
    if (isdir)
 
211
    if (!editmode && isdir)
311
212
    {
312
 
        setup_for_listing();
313
 
        home_listing(listing, subjects, path);
 
213
        var listing = response.responseText;
 
214
        /* The listing SHOULD be valid JSON text. Parse it into an object. */
 
215
        try
 
216
        {
 
217
            listing = JSON.parse(listing);
 
218
        }
 
219
        catch (e)
 
220
        {
 
221
            handle_error("The server returned an invalid directory listing");
 
222
            return;
 
223
        }
 
224
        handle_dir_listing(path, listing);
314
225
    }
315
226
    else
316
227
    {
317
 
        /* Need to make a 2nd ajax call, this time get the actual file
318
 
         * contents */
319
 
        callback = function(response)
320
 
            {
321
 
                /* Read the response and set up the page accordingly */
322
 
                handle_contents_response(path, response);
323
 
            }
324
 
        /* Call the server and request the listing. */
325
 
        if (url_args)
326
 
            args = shallow_clone_object(url_args);
327
 
        else
328
 
            args = {};
329
 
        /* This time, get the contents of the file, not its metadata */
330
 
        args['return'] = "contents";
331
 
        ajax_call(callback, service_app, path, args, "GET");
332
 
    }
333
 
    update_actions(isdir);
334
 
}
335
 
 
336
 
function handle_contents_response(path, response)
337
 
{
338
 
    /* Treat this as an ordinary file. Get the file type. */
339
 
    var content_type = response.getResponseHeader("Content-Type");
340
 
    var handler_type = get_handler_type(content_type);
341
 
    would_be_handler_type = handler_type;
342
 
    /* handler_type should now be set to either
343
 
     * "text", "image", "audio" or "binary". */
344
 
    switch (handler_type)
345
 
    {
346
 
    case "text":
347
 
        handle_text(path, response.responseText,
348
 
            would_be_handler_type);
349
 
        break;
350
 
    case "image":
351
 
        /* TODO: Custom image handler */
352
 
        handle_binary(path, response.responseText);
353
 
        break;
354
 
    case "audio":
355
 
        /* TODO: Custom audio handler */
356
 
        handle_binary(path, response.responseText);
357
 
        break;
358
 
    case "binary":
359
 
        handle_binary(path);
360
 
        break;
361
 
    }
362
 
}
363
 
 
364
 
/* Called when a form upload comes back (from an iframe).
365
 
 * Refreshes the page.
366
 
 */
367
 
function upload_callback()
368
 
{
369
 
    /* This has a pretty nasty hack, which happens to work.
370
 
     * upload_callback is set as the "onload" callback for the iframe which
371
 
     * receives the response from the server for uploading a file.
372
 
     * This means it gets called twice. Once when initialising the iframe, and
373
 
     * a second time when the actual response comes back.
374
 
     * All we want to do is call navigate to refresh the page. But we CAN'T do
375
 
     * that on the first load or it will just go into an infinite cycle of
376
 
     * refreshing. We need to refresh the page ONLY on the second refresh.
377
 
     * upload_callback_count is reset to 0 just before the iframe is created.
378
 
     */
379
 
    upload_callback_count++;
380
 
    if (upload_callback_count >= 2)
381
 
    {
382
 
        myFrame = frames['upload_iframe'].document;
383
 
        /* Browsers will turn the raw returned JSON into an HTML document. We
384
 
         * need to get the <pre> from inside the <body>, and look at its text.
385
 
         */
386
 
        data = myFrame.firstChild.getElementsByTagName(
387
 
            'body')[0].firstChild.firstChild.nodeValue;
388
 
        data = JSON.parse(data);
389
 
        if ('Error' in data)
390
 
            alert("Error: " + decodeURIComponent(data['Error']));
391
 
        document.getElementsByName('data')[0].value = '';
392
 
        refresh();
 
228
        /* Treat this as an ordinary file. Get the file type. */
 
229
        var content_type = response.getResponseHeader("Content-Type");
 
230
        var handler_type = get_handler_type(content_type);
 
231
        /* If we're in "edit mode", always treat this file as text */
 
232
        would_be_handler_type = handler_type;
 
233
        if (editmode) handler_type = "text";
 
234
        /* handler_type should now be set to either
 
235
         * "text", "image", "audio" or "binary". */
 
236
        switch (handler_type)
 
237
        {
 
238
        case "text":
 
239
            if (isdir)
 
240
            {
 
241
                handle_text(path_join(path, "untitled"), "",
 
242
                    would_be_handler_type);
 
243
            }
 
244
            else
 
245
            {
 
246
                handle_text(path, response.responseText,
 
247
                    would_be_handler_type);
 
248
            }
 
249
            break;
 
250
        case "image":
 
251
            /* TODO: Custom image handler */
 
252
            handle_binary(path, response.responseText);
 
253
            break;
 
254
        case "audio":
 
255
            /* TODO: Custom audio handler */
 
256
            handle_binary(path, response.responseText);
 
257
            break;
 
258
        case "binary":
 
259
            handle_binary(path);
 
260
            break;
 
261
        }
393
262
    }
394
263
}
395
264
 
399
268
 */
400
269
function clearpage()
401
270
{
 
271
    dom_removechildren(document.getElementById("path"));
402
272
    dom_removechildren(document.getElementById("filesbody"));
403
273
}
404
274
 
405
 
/* Checks if a file needs to be saved. If it does, the user will be asked
406
 
 * if they want to continue anyway. The caller must specify a warning
407
 
 * sentence which indicates the consequences of continuing.
408
 
 * Returns true if we should continue, and false if we should not.
409
 
 */
410
 
function maybe_save(warning)
411
 
{
412
 
    if (warning == null) warning = '';
413
 
    if (current_file == null || current_file.isdir) return true;
414
 
    if (document.getElementById("save_button").disabled) return true;
415
 
    return confirm("This file has unsaved changes. " + warning +
416
 
                   "\nAre you sure you wish to continue?");
417
 
}
418
 
 
419
275
/** Deletes all "dynamic" content on the page necessary to navigate from
420
276
 * one directory listing to another (does not clear as much as clearpage
421
277
 * does).
429
285
    dom_removechildren(document.getElementById("sidepanel"));
430
286
}
431
287
 
 
288
/** Sets the mode to either "file browser" or "text editor" mode.
 
289
 * This modifies the window icon, and selected tab.
 
290
 * \param editmode If True, editor mode. Else, file browser mode.
 
291
 */
 
292
function setmode(editmode)
 
293
{
 
294
    /* Find the DOM elements for the file browser and editor tabs */
 
295
    var tabs = document.getElementById("apptabs");
 
296
    var tab_files = null;
 
297
    var tab_edit = null;
 
298
    var a;
 
299
    var href;
 
300
    for (var i=0; i<tabs.childNodes.length; i++)
 
301
    {
 
302
        /* Find the href of the link within */
 
303
        if (!tabs.childNodes[i].getElementsByTagName) continue;
 
304
        a = tabs.childNodes[i].getElementsByTagName("a");
 
305
        if (a.length == 0) continue;
 
306
        href = a[0].getAttribute("href");
 
307
        if (href == null) continue;
 
308
        if (endswith(href, this_app))
 
309
            tab_files = tabs.childNodes[i];
 
310
        else if (endswith(href, edit_app))
 
311
            tab_edit = tabs.childNodes[i];
 
312
    }
 
313
 
 
314
    if (editmode)
 
315
    {
 
316
        tab_files.removeAttribute("class");
 
317
        tab_edit.setAttribute("class", "thisapp");
 
318
    }
 
319
    else
 
320
    {
 
321
        tab_edit.removeAttribute("class");
 
322
        tab_files.setAttribute("class", "thisapp");
 
323
    }
 
324
}
 
325
 
432
326
/*** HANDLERS for different types of responses (such as dir listing, file,
433
327
 * etc). */
434
328
 
435
 
/* handle_error.
436
 
 * message may either be a string, or a DOM node, which will be placed inside
437
 
 * a div.
438
 
 */
439
329
function handle_error(message)
440
330
{
 
331
    setmode(false);
441
332
    var files = document.getElementById("filesbody");
442
 
    var txt_elem;
443
 
    if (typeof(message) == "string")
444
 
    {
445
 
        txt_elem = dom_make_text_elem("div", "Error: "
446
 
                   + message.toString() + ".")
447
 
    }
448
 
    else
449
 
    {
450
 
        /* Assume message is a DOM node */
451
 
        txt_elem = document.createElement("div");
452
 
        txt_elem.appendChild(message);
453
 
    }
 
333
    var txt_elem = dom_make_text_elem("div", "Error: "
 
334
        + message.toString() + ".")
454
335
    txt_elem.setAttribute("class", "padding error");
455
336
    files.appendChild(txt_elem);
456
337
}
457
338
 
458
 
/** Given a path, filename and optional revision, returns a URL to open that
459
 
 *  revision of that file.
 
339
/** Presents a path list (address bar inside the page) for clicking.
460
340
 */
461
 
function build_revision_url(path, filename, revision)
 
341
function presentpath(path)
462
342
{
463
 
    bits = {'path': app_path(this_app, path, filename)};
464
 
    if (current_revision)
 
343
    var dom_path = document.getElementById("path");
 
344
    var href_path = make_path(this_app);
 
345
    var nav_path = "";
 
346
    var dir;
 
347
 
 
348
    /* Also set the document title */
 
349
    document.title = path_basename(path) + " - IVLE";
 
350
    /* Create all of the paths */
 
351
    var pathlist = path.split("/");
 
352
    for (var i=0; i<pathlist.length; i++)
465
353
    {
466
 
        bits['query_string'] = 'r=' + revision;
 
354
        dir = pathlist[i];
 
355
        if (dir == "") continue;
 
356
        /* Make an 'a' element */
 
357
        href_path = path_join(href_path, dir);
 
358
        nav_path = path_join(nav_path, dir);
 
359
        var link = dom_make_link_elem("a", dir, "Navigate to " + nav_path,
 
360
                href_path/*, "navigate(" + repr(href_path) + ")"*/);
 
361
        dom_path.appendChild(link);
 
362
        dom_path.appendChild(document.createTextNode("/"));
467
363
    }
468
 
    return build_url(bits);
 
364
    dom_path.removeChild(dom_path.lastChild);
469
365
}
470
366
 
471
367
/** Given a mime type, returns the path to the icon.
517
413
 */
518
414
function handle_binary(path)
519
415
{
 
416
    setmode(false);
520
417
    var files = document.getElementById("filesbody");
521
418
    var div = document.createElement("div");
522
419
    files.appendChild(div);
531
428
    div.appendChild(par2);
532
429
}
533
430
 
534
 
/* Enable or disable actions1 moreactions actions. Takes either a single
535
 
 * name, or an array of them.*/
536
 
function set_action_state(names, which, allow_on_revision)
537
 
{
538
 
    if (!(names instanceof Array)) names = Array(names);
539
 
 
540
 
    for (var i=0; i < names.length; i++)
541
 
    {
542
 
        element = document.getElementById('act_' + names[i]);
543
 
        if (which &&
544
 
            !(current_file.svnstatus == 'revision' && !allow_on_revision))
545
 
        {
546
 
            /* Enabling */
547
 
            element.setAttribute("class", "choice");
548
 
            element.removeAttribute("disabled");
549
 
        }
550
 
        else
551
 
        {
552
 
            /* Disabling */
553
 
            element.setAttribute("class", "disabled");
554
 
            element.setAttribute("disabled", "disabled");
555
 
        }
556
 
    }
557
 
}
558
 
 
559
 
/* Updates the list of available actions based on files selected */
560
 
function update_actions()
561
 
{
562
 
    var file;
563
 
    var numsel = selected_files.length;
564
 
    var svn_selection = false;
565
 
    
566
 
    if (numsel > 0)
567
 
    {
568
 
        svn_selection = true;
569
 
        for (var i = 0; i < selected_files.length; i++){
570
 
            if (file_listing[selected_files[i]]["svnstatus"] == "unversioned")
571
 
            {
572
 
                svn_selection = false;        
573
 
            }
574
 
        }
575
 
    }
576
 
    
577
 
    if (numsel <= 1)
578
 
    {
579
 
        if (numsel == 0)
580
 
        {
581
 
            /* Display information about the current directory instead */
582
 
            filename = path_basename(current_path);
583
 
            file = current_file;
584
 
        }
585
 
        else if (numsel == 1)
586
 
        {
587
 
            filename = selected_files[0];
588
 
            file = file_listing[filename];
589
 
        }
590
 
 
591
 
        /* Update each action node in the topbar.
592
 
         * This includes enabling/disabling actions as appropriate, and
593
 
         * setting href/onclick attributes. */
594
 
    }
595
 
 
596
 
    /* Open */
597
 
    /* Available if exactly one file is selected */
598
 
    var open = document.getElementById("act_open");
599
 
    if (numsel == 1)
600
 
    {
601
 
        open.setAttribute("class", "choice");
602
 
        if (file.isdir)
603
 
            open.setAttribute("title",
604
 
                "Navigate to this directory in the file browser");
605
 
        else
606
 
            open.setAttribute("title",
607
 
                "Edit or view this file");
608
 
        open.setAttribute("href", build_revision_url(current_path, filename,
609
 
                                                     current_revision));
610
 
    }
611
 
    else
612
 
    {
613
 
        open.setAttribute("class", "disabled");
614
 
        open.removeAttribute("title");
615
 
        open.removeAttribute("href");
616
 
    }
617
 
 
618
 
    /* Serve */
619
 
    /* Available if zero or one files are selected,
620
 
     * and only if this is a file, not a directory */
621
 
    var serve = document.getElementById("act_serve");
622
 
    if (numsel <= 1 && !file.isdir && current_file.svnstatus != 'revision')
623
 
    {
624
 
        serve.setAttribute("class", "choice");
625
 
        serve.setAttribute("onclick",
626
 
              "return maybe_save('The last saved version will be served.')");
627
 
        if (numsel == 0)
628
 
            serve.setAttribute("href",
629
 
                app_path(serve_app, current_path));
630
 
        else
631
 
            serve.setAttribute("href",
632
 
                app_path(serve_app, current_path, filename));
633
 
    }
634
 
    else
635
 
    {
636
 
        serve.setAttribute("class", "disabled");
637
 
        serve.removeAttribute("href");
638
 
        serve.removeAttribute("onclick");
639
 
    }
640
 
 
641
 
    /* Run */
642
 
    /* Available if exactly one file is selected,
643
 
     * and it is a Python file.
644
 
     */
645
 
    var run = document.getElementById("act_run");
646
 
     
647
 
    if (numsel <= 1 && !file.isdir && file.type == "text/x-python" 
648
 
            && current_file.svnstatus != 'revision')
649
 
    {
650
 
        if (numsel == 0)
651
 
        {
652
 
            // In the edit window
653
 
            var localpath = path_join('/home', current_path);
654
 
        }
655
 
        else
656
 
        {
657
 
            // In the browser window
658
 
            var localpath = path_join('/home', current_path, filename);
659
 
        }
660
 
        run.setAttribute("class", "choice");
661
 
        run.setAttribute("onclick", "runfile('" + localpath + "')");
662
 
    }
663
 
    else
664
 
    {
665
 
        run.setAttribute("class", "disabled");
666
 
        run.removeAttribute("onclick");
667
 
    }
668
 
 
669
 
    /* Download */
670
 
    /* Always available for current files.
671
 
     * If 0 files selected, download the current file or directory as a ZIP.
672
 
     * If 1 directory selected, download it as a ZIP.
673
 
     * If 1 non-directory selected, download it.
674
 
     * If >1 files selected, download them all as a ZIP.
675
 
     */
676
 
    var download = document.getElementById("act_download");
677
 
    if (current_file.svnstatus == 'revision')
678
 
    {
679
 
        download.setAttribute("class", "disabled");
680
 
        download.removeAttribute("onclick");
681
 
    }
682
 
    else if (numsel <= 1)
683
 
    {
684
 
        download.setAttribute("class", "choice")
685
 
        if (numsel == 0)
686
 
        {
687
 
            download.setAttribute("href",
688
 
                app_path(download_app, current_path));
689
 
            if (file.isdir)
690
 
                download.setAttribute("title",
691
 
                    "Download the current directory as a ZIP file");
692
 
            else
693
 
                download.setAttribute("title",
694
 
                    "Download the current file");
695
 
        }
696
 
        else
697
 
        {
698
 
            download.setAttribute("href",
699
 
                app_path(download_app, current_path, filename));
700
 
            if (file.isdir)
701
 
                download.setAttribute("title",
702
 
                    "Download the selected directory as a ZIP file");
703
 
            else
704
 
                download.setAttribute("title",
705
 
                    "Download the selected file");
706
 
        }
707
 
    }
708
 
    else
709
 
    {
710
 
        /* Make a query string with all the files to download */
711
 
        var dlpath = urlencode_path(app_path(download_app, current_path)) + "?";
712
 
        for (var i=0; i<numsel; i++)
713
 
            dlpath += "path=" + encodeURIComponent(selected_files[i]) + "&";
714
 
        dlpath = dlpath.substr(0, dlpath.length-1);
715
 
        download.setAttribute("class", "choice")
716
 
        download.setAttribute("href", dlpath);
717
 
        download.setAttribute("title",
718
 
            "Download the selected files as a ZIP file");
719
 
    }
720
 
 
721
 
    /* Refresh - No changes required */
722
 
 
723
 
    /* Publish and Submit */
724
 
    /* If this directory is under subversion and selected/unselected file is a
725
 
     * directory. */
726
 
    var publish = document.getElementById("act_publish");
727
 
    var submit = document.getElementById("act_submit");
728
 
    var pubcond = numsel <= 1 && file.isdir;
729
 
    if (pubcond)
730
 
    {
731
 
        /* If this dir is already published, call it "Unpublish" */
732
 
        if (file.published)
733
 
        {
734
 
            publish.setAttribute("value", "unpublish");
735
 
            publish.setAttribute("title" ,"Make it so this directory "
736
 
                + "can not be seen by anyone on the web");
737
 
            publish.firstChild.nodeValue = "Unpublish";
738
 
        } else {
739
 
            publish.setAttribute("value", "publish");
740
 
            publish.setAttribute("title","Make it so this directory "
741
 
                + "can be seen by anyone on the web");
742
 
            publish.firstChild.nodeValue = "Publish";
743
 
        }
744
 
    }
745
 
    set_action_state(["publish", "submit"], pubcond);
746
 
 
747
 
    /* Share */
748
 
    /* If exactly 1 non-directory file is selected, and its parent
749
 
     * directory is published.
750
 
     */
751
 
    set_action_state("share", numsel == 1 && !file.isdir &&
752
 
                     current_file.published);
753
 
 
754
 
    /* Rename */
755
 
    /* If exactly 1 file is selected */
756
 
    set_action_state("rename", numsel == 1);
757
 
 
758
 
    /* Delete, cut, copy */
759
 
    /* If >= 1 file is selected */
760
 
    set_action_state(["delete", "cut", "copy"], numsel >= 1);
761
 
 
762
 
    /* Paste, new file, new directory, upload */
763
 
    /* Disable if the current file is not a directory */
764
 
    set_action_state(["paste", "newfile", "mkdir", "upload"], current_file.isdir);
765
 
 
766
 
    /* Subversion actions */
767
 
    /* These are only useful if we are in a versioned directory and have some
768
 
     * files selected. */
769
 
    set_action_state(["svnadd"], numsel >= 1 && current_file.svnstatus);
770
 
    /* And these are only usefull is ALL the selected files are versioned */
771
 
    set_action_state(["svnremove", "svnrevert", "svncommit", "svncopy", 
772
 
            "svncut"], numsel >= 1 && current_file.svnstatus && svn_selection);
773
 
    
774
 
    /* Diff, log and update only support one path at the moment, so we must
775
 
     * have 0 or 1 versioned files selected. If 0, the directory must be
776
 
     * versioned. */
777
 
    single_versioned_path = (
778
 
         (
779
 
          (numsel == 1 && (svnst = file_listing[selected_files[0]].svnstatus)) ||
780
 
          (numsel == 0 && (svnst = current_file.svnstatus))
781
 
         ) && svnst != "unversioned");
782
 
    set_action_state(["svndiff", "svnupdate"], single_versioned_path);
783
 
 
784
 
    /* We can resolve if we have a file selected and it is conflicted. */
785
 
    set_action_state("svnresolved", single_versioned_path && numsel == 1 && svnst == "conflicted");
786
 
 
787
 
    /* Log should be available for revisions as well. */
788
 
    set_action_state("svnlog", single_versioned_path, true);
789
 
 
790
 
    /* Cleanup should be available for revisions as well. */
791
 
    set_action_state("svncleanup", single_versioned_path, true);
792
 
 
793
 
    single_ivle_versioned_path = (
794
 
         (
795
 
          (numsel == 1 && (stat = file_listing[selected_files[0]])) ||
796
 
          (numsel == 0 && (stat = current_file))
797
 
         ) && stat.svnstatus != "unversioned"
798
 
           && stat.svnurl
799
 
           && stat.svnurl.substr(0, svn_base.length) == svn_base);
800
 
    set_action_state(["submit"], single_ivle_versioned_path);
801
 
 
802
 
    /* There is currently nothing on the More Actions menu of use
803
 
     * when the current file is not a directory. Hence, just remove
804
 
     * it entirely.
805
 
     * (This makes some of the above decisions somewhat redundant).
806
 
     * We also take this opportunity to show the appropriate actions2
807
 
     * bar for this path. It should either be a save or upload widget.
808
 
     */
809
 
    if (current_file.isdir)
810
 
    {
811
 
        var actions2_directory = document.getElementById("actions2_directory");
812
 
        actions2_directory.setAttribute("style", "display: inline;");
813
 
        var moreactions = document.getElementById("moreactions_area");
814
 
        moreactions.setAttribute("style", "display: inline;");
815
 
    }
816
 
    else
817
 
    {
818
 
        var actions2_file = document.getElementById("actions2_file");
819
 
        actions2_file.setAttribute("style", "display: inline;");
820
 
    }
821
 
 
822
 
    return;
823
 
}
824
 
 
825
 
/** Event handler for when an item of the "More actions..." dropdown box is
826
 
 * selected. Performs the selected action. */
827
 
function handle_moreactions()
828
 
{
829
 
    var moreactions = document.getElementById("moreactions");
830
 
    if (moreactions.value == "top")
831
 
        return;
832
 
    var selectedaction = moreactions.value;
833
 
    /* Reset to "More actions..." */
834
 
    moreactions.selectedIndex = 0;
835
 
 
836
 
    /* If 0 files selected, filename is the name of the current dir.
837
 
     * If 1 file selected, filename is that file.
838
 
     */
839
 
    if (selected_files.length == 0)
840
 
        filename = path_basename(current_path);
841
 
    else if (selected_files.length == 1)
842
 
        filename = selected_files[0];
843
 
    else
844
 
        filename = null;
845
 
 
846
 
    /* Now handle the selected action */
847
 
    switch(selectedaction)
848
 
    {
849
 
    case "publish":
850
 
        action_publish(selected_files);
851
 
        break;
852
 
    case "unpublish":
853
 
        action_unpublish(selected_files);
854
 
        break;
855
 
    case "share":
856
 
        window.open(public_app_path("~" + current_path, filename), 'share')
857
 
        break;
858
 
    case "submit":
859
 
        if (selected_files.length == 1)
860
 
            stat = file_listing[selected_files[0]];
861
 
        else
862
 
            stat = current_file;
863
 
        path = stat.svnurl.substr(svn_base.length);
864
 
 
865
 
        /* The working copy might not have an up-to-date version of the
866
 
         * directory. While submitting like this could yield unexpected
867
 
         * results, we should really submit the latest revision to minimise
868
 
         * terrible mistakes - so we run off and ask fileservice for the
869
 
         * latest revision.*/
870
 
        $.post(app_path(service_app, current_path),
871
 
            {"action": "svnrepostat", "path": path},
872
 
            function(result)
873
 
            {
874
 
                window.location = path_join(app_path('+submit'), path) + '?revision=' + result.svnrevision;
875
 
            },
876
 
            "json");
877
 
 
878
 
        break;
879
 
    case "rename":
880
 
        action_rename(filename);
881
 
        break;
882
 
    case "delete":
883
 
        action_delete(selected_files);
884
 
        break;
885
 
    case "copy":
886
 
        action_copy(selected_files);
887
 
        break;
888
 
    case "cut":
889
 
        action_cut(selected_files);
890
 
        break;
891
 
    case "paste":
892
 
        action_paste();
893
 
        break;
894
 
    case "newfile":
895
 
        action_newfile();
896
 
        break;
897
 
    case "mkdir":
898
 
        action_mkdir();
899
 
        break;
900
 
    case "upload":
901
 
        show_uploadpanel(true);
902
 
        break;
903
 
    case "svnadd":
904
 
        action_add(selected_files);
905
 
        break;
906
 
    case "svnremove":
907
 
        action_remove(selected_files);
908
 
        break;
909
 
    case "svnrevert":
910
 
        action_revert(selected_files);
911
 
        break;
912
 
    case "svndiff":
913
 
        window.location = path_join(app_path('diff'), current_path, selected_files[0] || '');
914
 
        break;
915
 
    case "svnupdate":
916
 
        action_update(selected_files);
917
 
        break;
918
 
    case "svnresolved":
919
 
        action_resolved(selected_files);
920
 
        break;
921
 
    case "svncommit":
922
 
        action_commit(selected_files);
923
 
        break;
924
 
    case "svnlog":
925
 
        window.location = path_join(app_path('svnlog'), current_path, selected_files[0] || '');
926
 
        break;
927
 
    case "svncopy":
928
 
        action_svncopy(selected_files);
929
 
        break;
930
 
    case "svncut":
931
 
        action_svncut(selected_files);
932
 
        break;
933
 
    case "svncleanup":
934
 
        action_svncleanup(".");
935
 
        break;
936
 
    }
937
 
}
938
 
 
939
 
/** User clicks "Run" button.
940
 
 * Do an Ajax call and print the test output.
941
 
 */
942
 
function runfile(localpath)
943
 
{
944
 
    if (!maybe_save('The last saved version will be run.')) return false;
945
 
 
946
 
    /* Dump the entire file to the console */
947
 
    var callback = function()
948
 
    {
949
 
        console_enter_line("execfile('" + localpath + "')", "block");
950
 
    }
951
 
    start_server(callback)
952
 
    return;
953
 
}
954
 
 
955
431
/** Called when the page loads initially.
956
432
 */
957
 
function browser_init()
 
433
window.onload = function()
958
434
{
959
435
    /* Navigate (internally) to the path in the URL bar.
960
436
     * This causes the page to be populated with whatever is at that address,
961
437
     * whether it be a directory or a file.
962
438
     */
963
 
    var path = get_path();
964
 
    navigate(path);
965
 
}
966
 
 
967
 
/** Gets the current path of the window */
968
 
function get_path() {
969
439
    var path = parse_url(window.location.href).path;
970
440
    /* Strip out root_dir + "/files" from the front of the path */
971
441
    var strip = make_path(this_app);
 
442
    var editmode = false;
972
443
    if (path.substr(0, strip.length) == strip)
973
444
        path = path.substr(strip.length+1);
974
445
    else
978
449
        if (path.substr(0, strip.length) == strip)
979
450
        {
980
451
            path = path.substr(strip.length+1);
 
452
            editmode = true;
981
453
        }
982
454
    }
983
455
 
988
460
        path = username;
989
461
    }
990
462
 
991
 
    return path;
 
463
    navigate(path, editmode);
992
464
}