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

« back to all changes in this revision

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

Fixed an oversight in the tutorial code which was printing <worksheet>
and <exercise> tags into the output

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
 * "text" : When navigating to a text file, the text editor is opened.
32
32
 * "image" : When navigating to an image, the image is displayed (rather than
33
33
 *              going to the text editor).
34
 
 * "video" : When navigation to a video file, a "play" button is presented.
35
34
 * "audio" : When navigating to an audio file, a "play" button is presented.
36
35
 * "binary" : When navigating to a binary file, offer it as a download through
37
36
 *              "serve".
44
43
    "application/x-javascript" : "text",
45
44
    "application/javascript" : "text",
46
45
    "application/json" : "text",
47
 
    "application/xml" : "text",
48
 
    "application/ogg" : "audio",
49
 
    "image/svg+xml": "object"
 
46
    "application/xml" : "text"
50
47
};
51
48
 
52
49
/* Mapping MIME types to icons, just the file's basename */
58
55
default_type_icon = "txt.png";
59
56
 
60
57
/* Relative to IVLE root */
61
 
type_icons_path = "+media/ivle.webapp.core/images/mime";
62
 
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";
63
60
 
64
61
/* Mapping SVN status to icons, just the file's basename */
65
62
svn_icons = {
66
 
    "unversioned": "unversioned.png",
67
 
    "ignored": null,                    /* Supposed to be innocuous */
 
63
    "unversioned": null,
68
64
    "normal": "normal.png",
69
65
    "added": "added.png",
70
66
    "missing": "missing.png",
71
67
    "deleted": "deleted.png",
72
 
    "replaced": "replaced.png",
73
68
    "modified": "modified.png",
74
69
    "conflicted": "conflicted.png",
75
70
    "revision": "revision.png"
78
73
/* Mapping SVN status to "nice" strings */
79
74
svn_nice = {
80
75
    "unversioned": "Temporary file",
81
 
    "ignored": "Temporary file (ignored)",
82
76
    "normal": "Permanent file",
83
77
    "added": "Temporary file (scheduled to be added)",
84
78
    "missing": "Permanent file (missing)",
93
87
default_svn_icon = null;
94
88
default_svn_nice = "Unknown status";
95
89
 
96
 
svn_icons_path = "+media/ivle.webapp.core/images/svn";
 
90
svn_icons_path = "media/images/svn";
97
91
 
98
 
published_icon = "+media/ivle.webapp.core/images/interface/published.png";
 
92
published_icon = "media/images/interface/published.png";
99
93
 
100
94
/* List of MIME types considered "executable" by the system.
101
95
 * Executable files offer a "run" link, implying that the "serve"
140
134
 *      May be "application/x-www-form-urlencoded" or "multipart/form-data".
141
135
 *      Defaults to "application/x-www-form-urlencoded".
142
136
 *      "multipart/form-data" is recommended for large uploads.
143
 
 * \param callback, optional.
144
 
 *      A callback function for after the action has been handled.
145
137
 */
146
 
function do_action(action, path, args, content_type, callback)
 
138
function do_action(action, path, args, content_type, ignore_response)
147
139
{
148
140
    args.action = action;
149
141
    /* Callback action, when the server returns */
150
 
    var callback_inner = function(response)
 
142
    var callback = function(response)
151
143
        {
152
144
            /* Check for action errors reported by the server, and report them
153
145
             * to the user */
154
146
            var error = response.getResponseHeader("X-IVLE-Action-Error");
155
 
            if (error != null && error != "")
 
147
            if (error != null)
156
148
                /* Note: This header (in particular) comes URI-encoded, to
157
149
                 * allow multi-line error messages. Decode */
158
150
                alert("Error: " + decodeURIComponent(error.toString()) + ".");
159
151
            /* Now read the response and set up the page accordingly */
160
 
            if (callback != null)
161
 
                callback(path, response);
 
152
            if (ignore_response != true)
 
153
                handle_response(path, response, true);
162
154
        }
163
155
    /* Call the server and perform the action. This mutates the server. */
164
 
    ajax_call(callback_inner, service_app, path, args, "POST", content_type);
 
156
    ajax_call(callback, service_app, path, args, "POST", content_type);
165
157
}
166
158
 
167
159
/** Calls the server using Ajax, requesting a directory listing. This should
196
188
}
197
189
 
198
190
/** Determines the "handler type" from a MIME type.
199
 
 * The handler type is a string, either "text", "image", "video", "audio", 
200
 
 * "object" or "binary".
 
191
 * The handler type is a string, either "text", "image", "audio" or "binary".
201
192
 */
202
193
function get_handler_type(content_type)
203
194
{
209
200
    {   /* Based on the first part of the MIME type */
210
201
        var handler_type = content_type.split('/')[0];
211
202
        if (handler_type != "text" && handler_type != "image" &&
212
 
            handler_type != "video" && handler_type != "audio")
 
203
            handler_type != "audio")
213
204
            handler_type = "binary";
214
205
        return handler_type;
215
206
    }
251
242
    }
252
243
 
253
244
    var subjects = null;
254
 
    /* Remove trailing slash (or path==username won't compare properly) */
255
 
    if (path[path.length-1] == "/")
256
 
        path = path.substr(0, path.length-1);
257
245
    var top_level_dir = path==username;
258
246
    if (top_level_dir)
259
247
    {
320
308
    if (isdir)
321
309
    {
322
310
        setup_for_listing();
323
 
        if (top_level_dir)
324
 
        {
325
 
            /* Top-level dir, with subjects */
326
 
            special_home_listing(listing, subjects, path);
327
 
        }
328
 
        else
329
 
        {
330
 
            /* Not the top-level dir. Do a normal dir listing. */
331
 
            handle_dir_listing(path, listing.listing);
332
 
        }
 
311
        home_listing(listing, subjects, path);
333
312
    }
334
313
    else
335
314
    {
336
 
        /* Read the response and set up the page accordingly */
337
 
        var content_type = current_file.type;
338
 
        handle_contents_response(path, content_type, url_args);
339
 
 
 
315
        /* Need to make a 2nd ajax call, this time get the actual file
 
316
         * contents */
 
317
        callback = function(response)
 
318
            {
 
319
                /* Read the response and set up the page accordingly */
 
320
                handle_contents_response(path, response);
 
321
            }
 
322
        /* Call the server and request the listing. */
 
323
        if (url_args)
 
324
            args = shallow_clone_object(url_args);
 
325
        else
 
326
            args = {};
 
327
        /* This time, get the contents of the file, not its metadata */
 
328
        args['return'] = "contents";
 
329
        ajax_call(callback, service_app, path, args, "GET");
340
330
    }
341
331
    update_actions(isdir);
342
332
}
343
333
 
344
 
function handle_contents_response(path, content_type)
 
334
function handle_contents_response(path, response)
345
335
{
346
336
    /* Treat this as an ordinary file. Get the file type. */
347
 
    //var content_type = response.getResponseHeader("Content-Type");
 
337
    var content_type = response.getResponseHeader("Content-Type");
348
338
    var handler_type = get_handler_type(content_type);
 
339
    would_be_handler_type = handler_type;
349
340
    /* handler_type should now be set to either
350
 
     * "text", "image", "video", "audio" or "binary". */
 
341
     * "text", "image", "audio" or "binary". */
351
342
    switch (handler_type)
352
343
    {
353
344
    case "text":
354
 
        handle_text(path, content_type);
 
345
        handle_text(path, response.responseText,
 
346
            would_be_handler_type);
355
347
        break;
356
348
    case "image":
357
 
        handle_image(path);
358
 
        break;
359
 
    case "video":
360
 
        handle_html5_media(path, content_type, "video");
 
349
        /* TODO: Custom image handler */
 
350
        handle_binary(path, response.responseText);
361
351
        break;
362
352
    case "audio":
363
 
        handle_html5_media(path, content_type, "audio");
364
 
        break;
365
 
    case "object":
366
 
        handle_object(path, content_type);
 
353
        /* TODO: Custom audio handler */
 
354
        handle_binary(path, response.responseText);
367
355
        break;
368
356
    case "binary":
369
357
        handle_binary(path);
390
378
    if (upload_callback_count >= 2)
391
379
    {
392
380
        myFrame = frames['upload_iframe'].document;
393
 
        /* Browsers will turn the raw returned JSON into an HTML document. We
394
 
         * need to get the <pre> from inside the <body>, and look at its text.
395
 
         */
396
 
        var pre = myFrame.firstChild.getElementsByTagName(
397
 
            'body')[0].firstChild;
398
 
        var data = pre.innerText || pre.textContent;
 
381
        data = myFrame.firstChild.childNodes[1].firstChild.firstChild.nodeValue;
399
382
        data = JSON.parse(data);
400
383
        if ('Error' in data)
401
384
            alert("Error: " + decodeURIComponent(data['Error']));
421
404
function maybe_save(warning)
422
405
{
423
406
    if (warning == null) warning = '';
424
 
    if (current_file == null || current_file.isdir) return true;
 
407
    if (current_file.isdir) return true;
425
408
    if (document.getElementById("save_button").disabled) return true;
426
409
    return confirm("This file has unsaved changes. " + warning +
427
410
                   "\nAre you sure you wish to continue?");
524
507
        return default_svn_nice;
525
508
}
526
509
 
527
 
/** Returns true if a file is versioned (not unversioned or ignored).
528
 
 */
529
 
function svnstatus_versioned(svnstatus)
530
 
{
531
 
    return svnstatus != "unversioned" && svnstatus != "ignored";
532
 
}
533
 
 
534
510
/** Displays a download link to the binary file.
535
511
 */
536
512
function handle_binary(path)
537
513
{
538
 
    // Disable save button
539
 
    using_codepress = false;
540
 
    disable_save();
541
 
 
542
 
    // Show download link
543
514
    var files = document.getElementById("filesbody");
544
515
    var div = document.createElement("div");
545
516
    files.appendChild(div);
546
517
    div.setAttribute("class", "padding");
547
 
    var download_link = app_url(download_app, path);
 
518
    var download_link = app_path(download_app, path);
548
519
    var par1 = dom_make_text_elem("p",
549
520
        "The file " + path + " is a binary file. To download this file, " +
550
521
        "click the following link:");
554
525
    div.appendChild(par2);
555
526
}
556
527
 
557
 
/** Displays an image file.
558
 
 */
559
 
function handle_image(path)
560
 
{
561
 
    /* Disable save button */
562
 
    using_codepress = false;
563
 
    disable_save();
564
 
 
565
 
    /* URL */
566
 
    var url = app_url(service_app, path) + "?return=contents";
567
 
 
568
 
    /* Image Preview */
569
 
    var img = $("<img />");
570
 
    img.attr("alt", path);
571
 
    img.attr("src", url);
572
 
 
573
 
    /* Show Preview */
574
 
    var div = $('<div class="padding" />');
575
 
    div.append('<h1>Image Preview</h1>');
576
 
    div.append(img);
577
 
    $("#filesbody").append(div);
578
 
}
579
 
 
580
 
/* Displays a media file using an HTML5 <audio> or <video> tag.
581
 
 * Falls back to <object> if the format is unsupported.
582
 
 */
583
 
function handle_html5_media(path, type, tag_name)
584
 
{
585
 
    /* Disable save button and hide the save panel */
586
 
    using_codepress = false;
587
 
    disable_save();
588
 
 
589
 
    /* URL */
590
 
    var url = app_url(service_app, path) + "?return=contents";
591
 
    var download_url = app_url(download_app, path);
592
 
 
593
 
    /* Fallback download link */
594
 
    var link = $(
595
 
        '<p>Could not play ' + tag_name + ' file. ' +
596
 
        'Try <a>downloading it</a> instead.</p>');
597
 
    link.find('a').attr("href", download_url);
598
 
 
599
 
    /* HTML 5 media element */
600
 
    var html5_element = $(
601
 
        '<' + tag_name + ' controls="true" autoplay="true" />');
602
 
    html5_element.attr("src", url);
603
 
    var support = (html5_element[0].canPlayType &&
604
 
                   html5_element[0].canPlayType(type));
605
 
 
606
 
    /* If the browser thinks it might be able to play it, use the HTML5
607
 
     * element. Otherwise, fall back to an <object>, which might work.
608
 
     */
609
 
    if (support == "probably" || support == "maybe") {
610
 
        var element = html5_element;
611
 
    } else {
612
 
        var element = $('<object />');
613
 
        element.attr("type", type);
614
 
        element.attr("data", url);
615
 
    }
616
 
    element.append(link);
617
 
 
618
 
    /* Show Preview */
619
 
    var div = $('<div class="padding" />');
620
 
    div.append('<h1>File preview</h1>');
621
 
    div.append(element);
622
 
    $("#filesbody").append(div);
623
 
}
624
 
 
625
 
/** Display generic object content
626
 
 */
627
 
function handle_object(path, content_type)
628
 
{
629
 
    /* Disable save button and hide the save panel */
630
 
    using_codepress = false;
631
 
    disable_save();
632
 
 
633
 
    /* URL */
634
 
    var url = app_url(service_app, path) + "?return=contents";
635
 
    var download_url = app_url(download_app, path);
636
 
 
637
 
    /* Fallback Download Link */
638
 
    var link = $('<p><a /></p>');
639
 
    var a = link.find('a');
640
 
    a.attr("href", download_url);
641
 
    a.text("Download " + path);
642
 
 
643
 
    /* Object Tag */
644
 
    var obj = $('<object width="100%" height="500px" />');
645
 
    obj.attr("type", content_type);
646
 
    obj.attr("data", url);
647
 
    obj.append('Could not load object');
648
 
 
649
 
    /* Show Preview */
650
 
    var div = $('<div class="padding" />');
651
 
    div.append('<h1>Preview</h1>');
652
 
    div.append(obj);
653
 
    div.append(link);
654
 
    $("#filesbody").append(div);
655
 
}
656
 
 
657
 
/* Present an element for the given path.
658
 
 * Gives it a title and download link.
659
 
 */
660
 
function present_custom_handler(path, type, element)
661
 
{
662
 
    /* Disable save button and hide the save panel */
663
 
    using_codepress = false;
664
 
    disable_save();
665
 
 
666
 
    /* URL */
667
 
    var url = app_url(service_app, path) + "?return=contents";
668
 
    var download_url = app_url(download_app, path);
669
 
 
670
 
    /* Fallback download link */
671
 
    var link = $(
672
 
        '<p>Could not play ' + tag_name + ' file. ' +
673
 
        'Try <a>downloading it</a> instead.</p>');
674
 
    link.find('a').attr("href", download_url);
675
 
 
676
 
    /* HTML 5 media element */
677
 
    var html5_element = $(
678
 
        '<' + tag_name + ' controls="true" autoplay="true" />');
679
 
    html5_element.attr("src", url);
680
 
    var support = (html5_element[0].canPlayType &&
681
 
                   html5_element[0].canPlayType(type));
682
 
 
683
 
    /* If the browser thinks it might be able to play it, use the HTML5
684
 
     * element. Otherwise, fall back to an <object>, which might work.
685
 
     */
686
 
    if (support == "probably" || support == "maybe") {
687
 
        var element = html5_element;
688
 
    } else {
689
 
        var element = $('<object />');
690
 
        element.attr("type", type);
691
 
        element.attr("data", url);
692
 
    }
693
 
    element.append(link);
694
 
 
695
 
    /* Show Preview */
696
 
    var div = $('<div class="padding" />');
697
 
    div.append('<h1>File preview</h1>');
698
 
    div.append(element);
699
 
    $("#filesbody").append(div);
700
 
}
701
 
 
702
528
/* Enable or disable actions1 moreactions actions. Takes either a single
703
529
 * name, or an array of them.*/
704
530
function set_action_state(names, which, allow_on_revision)
735
561
    {
736
562
        svn_selection = true;
737
563
        for (var i = 0; i < selected_files.length; i++){
738
 
            if (!svnstatus_versioned(file_listing[selected_files[i]].svnstatus))
 
564
            if (file_listing[selected_files[i]]["svnstatus"] == "unversioned")
739
565
            {
740
 
                svn_selection = false;
 
566
                svn_selection = false;        
741
567
            }
742
568
        }
743
569
    }
794
620
              "return maybe_save('The last saved version will be served.')");
795
621
        if (numsel == 0)
796
622
            serve.setAttribute("href",
797
 
                app_url(serve_app, current_path));
 
623
                app_path(serve_app, current_path));
798
624
        else
799
625
            serve.setAttribute("href",
800
 
                app_url(serve_app, current_path, filename));
 
626
                app_path(serve_app, current_path, filename));
801
627
    }
802
628
    else
803
629
    {
853
679
        if (numsel == 0)
854
680
        {
855
681
            download.setAttribute("href",
856
 
                app_url(download_app, current_path));
 
682
                app_path(download_app, current_path));
857
683
            if (file.isdir)
858
684
                download.setAttribute("title",
859
685
                    "Download the current directory as a ZIP file");
864
690
        else
865
691
        {
866
692
            download.setAttribute("href",
867
 
                app_url(download_app, current_path, filename));
 
693
                app_path(download_app, current_path, filename));
868
694
            if (file.isdir)
869
695
                download.setAttribute("title",
870
696
                    "Download the selected directory as a ZIP file");
876
702
    else
877
703
    {
878
704
        /* Make a query string with all the files to download */
879
 
        var dlpath = app_url(download_app, current_path) + "?";
 
705
        var dlpath = urlencode_path(app_path(download_app, current_path)) + "?";
880
706
        for (var i=0; i<numsel; i++)
881
707
            dlpath += "path=" + encodeURIComponent(selected_files[i]) + "&";
882
708
        dlpath = dlpath.substr(0, dlpath.length-1);
902
728
            publish.setAttribute("value", "unpublish");
903
729
            publish.setAttribute("title" ,"Make it so this directory "
904
730
                + "can not be seen by anyone on the web");
905
 
            publish.firstChild.nodeValue = "Unpublish";
 
731
            publish.textContent = "Unpublish";
906
732
        } else {
907
733
            publish.setAttribute("value", "publish");
908
734
            publish.setAttribute("title","Make it so this directory "
909
735
                + "can be seen by anyone on the web");
910
 
            publish.firstChild.nodeValue = "Publish";
 
736
            publish.textContent = "Publish";
911
737
        }
912
738
    }
913
739
    set_action_state(["publish", "submit"], pubcond);
934
760
    /* Subversion actions */
935
761
    /* These are only useful if we are in a versioned directory and have some
936
762
     * files selected. */
937
 
    set_action_state(["svnrename"], numsel == 1 && current_file.svnstatus);
938
 
    set_action_state(["svnadd"], numsel >= 1 && current_file.svnstatus);
939
 
    /* And these are only useful is ALL the selected files are versioned */
940
 
    set_action_state(["svnremove", "svnrevert", "svncopy", "svncut"],
941
 
            numsel >= 1 && current_file.svnstatus && svn_selection);
942
 
    /* Commit is useful if ALL selected files are versioned, or the current
943
 
     * directory is versioned */
944
 
    set_action_state(["svncommit"], current_file.svnstatus &&
945
 
            (numsel >= 1 && svn_selection || numsel == 0));
946
 
 
 
763
    set_action_state(["svnadd",], numsel >= 1 && current_file.svnstatus);
 
764
    /* And these are only usefull is ALL the selected files are versioned */
 
765
    set_action_state(["svnremove", "svnrevert", "svncommit", "svncopy", 
 
766
            "svncut"], numsel >= 1 && current_file.svnstatus && svn_selection);
 
767
    
947
768
    /* Diff, log and update only support one path at the moment, so we must
948
769
     * have 0 or 1 versioned files selected. If 0, the directory must be
949
770
     * versioned. */
951
772
         (
952
773
          (numsel == 1 && (svnst = file_listing[selected_files[0]].svnstatus)) ||
953
774
          (numsel == 0 && (svnst = current_file.svnstatus))
954
 
         ) && svnstatus_versioned(svnst));
 
775
         ) && svnst != "unversioned");
955
776
    set_action_state(["svndiff", "svnupdate"], single_versioned_path);
956
777
 
957
778
    /* We can resolve if we have a file selected and it is conflicted. */
960
781
    /* Log should be available for revisions as well. */
961
782
    set_action_state("svnlog", single_versioned_path, true);
962
783
 
963
 
    /* Cleanup should be available for revisions as well. */
964
 
    set_action_state("svncleanup", single_versioned_path, true);
965
 
 
966
 
    single_ivle_versioned_path = (
967
 
         (
968
 
          (numsel == 1 && (stat = file_listing[selected_files[0]])) ||
969
 
          (numsel == 0 && (stat = current_file))
970
 
         ) && svnstatus_versioned(stat.svnstatus)
971
 
           && stat.svnurl
972
 
           && stat.svnurl.substr(0, svn_base.length) == svn_base);
973
 
    set_action_state(["submit"], single_ivle_versioned_path);
974
 
 
975
784
    /* There is currently nothing on the More Actions menu of use
976
785
     * when the current file is not a directory. Hence, just remove
977
786
     * it entirely.
986
795
        var moreactions = document.getElementById("moreactions_area");
987
796
        moreactions.setAttribute("style", "display: inline;");
988
797
    }
 
798
    else
 
799
    {
 
800
        var actions2_file = document.getElementById("actions2_file");
 
801
        actions2_file.setAttribute("style", "display: inline;");
 
802
    }
989
803
 
990
804
    return;
991
805
}
1021
835
        action_unpublish(selected_files);
1022
836
        break;
1023
837
    case "share":
1024
 
        window.open(public_app_url("~" + current_path, filename), 'share')
 
838
        //alert("Not yet implemented: Sharing files");
 
839
        window.open(public_app_path(serve_app, current_path, filename), 'share')
1025
840
        break;
1026
841
    case "submit":
1027
 
        if (selected_files.length == 1)
1028
 
            stat = file_listing[selected_files[0]];
1029
 
        else
1030
 
            stat = current_file;
1031
 
        url = stat.svnurl.substr(svn_base.length);      // URL-encoded
1032
 
        path = decodeURIComponent(url);
1033
 
 
1034
 
        /* The working copy might not have an up-to-date version of the
1035
 
         * directory. While submitting like this could yield unexpected
1036
 
         * results, we should really submit the latest revision to minimise
1037
 
         * terrible mistakes - so we run off and ask fileservice for the
1038
 
         * latest revision.*/
1039
 
        $.post(app_path(service_app, current_path),
1040
 
            {"action": "svnrepostat", "path": path},
1041
 
            function(result)
1042
 
            {
1043
 
                window.location = path_join(app_path('+submit'), url) + '?revision=' + result.svnrevision;
1044
 
            },
1045
 
            "json");
1046
 
 
 
842
        // TODO
 
843
        alert("Not yet implemented: Submit");
1047
844
        break;
1048
845
    case "rename":
1049
846
        action_rename(filename);
1073
870
        action_add(selected_files);
1074
871
        break;
1075
872
    case "svnremove":
1076
 
        action_svnremove(selected_files);
1077
 
        break;
1078
 
    case "svnrename":
1079
 
        action_svnrename(selected_files);
 
873
        action_remove(selected_files);
1080
874
        break;
1081
875
    case "svnrevert":
1082
876
        action_revert(selected_files);
1083
877
        break;
1084
878
    case "svndiff":
1085
 
        window.location = path_join(app_url('diff'), current_path, selected_files[0] || '');
 
879
        window.location = path_join(app_path('diff'), current_path, selected_files[0] || '');
1086
880
        break;
1087
881
    case "svnupdate":
1088
882
        action_update(selected_files);
1094
888
        action_commit(selected_files);
1095
889
        break;
1096
890
    case "svnlog":
1097
 
        window.location = path_join(app_url('svnlog'), current_path, selected_files[0] || '');
 
891
        window.location = path_join(app_path('svnlog'), current_path, selected_files[0] || '');
1098
892
        break;
1099
893
    case "svncopy":
1100
894
        action_svncopy(selected_files);
1102
896
    case "svncut":
1103
897
        action_svncut(selected_files);
1104
898
        break;
1105
 
    case "svncleanup":
1106
 
        action_svncleanup(".");
1107
 
        break;
1108
899
    }
1109
900
}
1110
901
 
1132
923
     * This causes the page to be populated with whatever is at that address,
1133
924
     * whether it be a directory or a file.
1134
925
     */
1135
 
    var path = get_path();
1136
 
    navigate(path);
1137
 
}
1138
 
 
1139
 
/** Gets the current path of the window */
1140
 
function get_path() {
1141
926
    var path = parse_url(window.location.href).path;
1142
927
    /* Strip out root_dir + "/files" from the front of the path */
1143
928
    var strip = make_path(this_app);
1160
945
        path = username;
1161
946
    }
1162
947
 
1163
 
    return path;
 
948
    navigate(path);
1164
949
}