dialog_search.html 29.1 KB
Newer Older
1
2
3
<!DOCTYPE html>
<html>
  <head>
4
5
    <link id="page_style" rel="stylesheet"
          type="text/css" href="css/default.css">
6
    <title>Pd Search Engine</title>
7
    <script type="text/javascript" src="./console_search.js"></script>
8
9
10
11
12
13
14
    <script>
"use strict";
var pdgui = require("./pdgui.js");
var fs = require("fs");
var path = require("path");
var l = pdgui.get_local_string;

15
var index;
16
17
18

pdgui.skin.apply(window);

19
20
21
var filetypes = [".pd", ".txt", ".htm", ".html", ".pdf"];

// Table of Contents to start with
22
23
24
25
/* NOTE: A section title is indicated by an entry consisting just of the title
   field. All other entries *must* also contain the directory (relative to the
   pd-l2ork lib directory) in the id field, and may optionally contain a
   description field. */
26
var toc = [
27
    // Original Pd documentation
28
29
30
    {
        id: "doc/1.manual",
        title: "Pd Manual",
31
        description: "HTML manual"
32
33
34
35
    },
    {
        id: "doc/2.control.examples",
        title: "Control Tutorials",
36
        description: "the Pd language and editor"
37
38
39
40
    },
    {
        id: "doc/3.audio.examples",
        title: "Audio Tutorials",
41
        description: "manipulating audio in realtime"
42
43
    },
    {
44
        id: "doc/4.data.structures",
45
        title: "Data Structures",
46
        description: "custom data and 2d visualization"
47
48
49
    },
    {
        id: "doc/5.reference",
50
51
        title: "Reference",
        description: "help patches for all built-in object classes"
52
    },
53
/*
54
55
56
57
58
    {
        id: "doc/6.externs",
        title: "Externals",
        description: "how to code control and signal objects in C"
    },
59
    // Section 7 of the vanilla Pd docs, uncomment these if needed.
60
61
62
    {
        title: "Stuff",
    },
63
64
    {
        id: "doc/7.stuff/soundfile-tools",
65
        title: "Soundfile Tools",
66
67
68
69
        description: "some standard audio transformations packaged to work with mono soundfiles"
    },
    {
        id: "doc/7.stuff/synth",
70
        title: "Synth",
71
72
73
74
        description: "polyphonic subtractive synthesizer example"
    },
    {
        id: "doc/7.stuff/tools",
75
        title: "Tools",
76
77
78
79
80
81
        description: "useful little helper patches"
    },
*/
    // Pd-L2Ork extras
    {
        id: "doc/4.data.structures/pd-l2ork/ds-tutorials",
82
        title: "Pd-L2Ork Data Structures",
83
        description: "new and improved data structure visualizations"
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
    },
    // PDDP tutorials
    {
        title: "PDDP Tutorials",
    },
    {
        id: "doc/manuals/0.Intro",
        title: "Intro",
        description: "getting started with Pd"
    },
    {
        id: "doc/manuals/1.Sound",
        title: "Sound",
        description: "Pd sound examples"
    },
    {
        id: "doc/manuals/2.Image",
        title: "Image",
102
        description: "3D graphics with GEM (Windows and Linux only)"
103
104
105
106
107
108
109
110
111
112
113
114
115
    },
    {
        id: "doc/manuals/3.Networking",
        title: "Networking",
        description: "introduction to Pd's networking facilities"
    },
    // External libraries
    // NOTE: These are just some popular examples. Pd-L2Ork ships with
    // many external libraries, too many to list them all. Feel free
    // to edit this list as needed.
    {
        title: "Externals",
    },
116
117
118
119
120
    {
        id: "extra/cyclone",
        title: "Cyclone",
        description: "library of clones of Max/MSP 4.x objects"
    },
121
122
123
    {
        id: "extra/disis",
        title: "DISIS",
124
        description: "DISIS externals (Linux only)"
125
    },
126
127
128
    {
        id: "extra/Gem",
        title: "GEM",
129
        description: "Graphics Environment for Multimedia (Windows and Linux only)"
130
    },
131
132
133
134
135
    {
        id: "extra/lyon",
        title: "LyonPotpourri",
        description: "Eric Lyon's external collection"
    },
136
137
];

138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// ag: Some functions to add bookmarks to the toc, and save them in the user's
// personal config file.

function toc_add_bookmarks()
{
    toc[toc.length] = {
        title: "Bookmarks",
    };
}

function toc_bookmarks()
{
    for (var i = 0, len = toc.length; i < len; i++) {
	if (!toc[i].id && !toc[i].description &&
	    toc[i].title == "Bookmarks") {
	    return i;
	}
    }
    return -1;
}

function expand_tilde(filepath) {
    if (filepath[0] === '~') {
        var home = pdgui.check_os("win32") ? process.env.HOMEPATH :
	    process.env.HOME;
        return path.join(home, filepath.slice(1));
    }
    return filepath;
}

const toc_config = expand_tilde(
    pdgui.check_os("win32")
	? "~/AppData/Roaming/Purr-Data/bookmarks.json"
	: "~/.purr-data/bookmarks.json"
);

function toc_save()
{
    var i = toc_bookmarks();
    if (i >= 0) {
	// the actual bookmarks start at index i+1, i is the section title
	var data = JSON.stringify(toc.slice(i+1), null, 2);
	fs.writeFileSync(toc_config, data);
    } else {
	// no bookmarks, get rid of the bookmarks file if present
	try {
	    fs.unlinkSync(toc_config);
	} catch (err) {
	    // ignore
	}
    }
}

// this should be executed just once, when the browser is first shown
function toc_load()
{
    function toc_valid(doc) {
	// validate the bookmark entries
	if (doc.id && doc.title) {
	    try {
		fs.accessSync(check_dir(doc.id), fs.F_OK);
		return true;
	    } catch (e) {
		return false;
	    }
	} else {
	    return false;
	}
    };
    var bookmarks;
    try {
	bookmarks = fs.readFileSync(toc_config);
	try {
	    bookmarks = JSON.parse(bookmarks);
	} catch (err) {
	    // might be a syntax error, if the user edited the file manually,
	    // so lets provide some (hopefully useful) diagnostic in the
	    // console
	    pdgui.post("error reading bookmarks: "+toc_config, "error");
	    pdgui.post(err);
	    return;
	}
    } catch (err) {
	// no bookmarks, just bail out
	return;
    }
    try {
	// this might still fail if the JSON we read is some (syntactically
	// correct) random garbage, in this case give some diagnostic below 
	bookmarks = bookmarks.filter(toc_valid);
	if (bookmarks && bookmarks.length > 0) {
	    toc_add_bookmarks();
	    toc = toc.concat(bookmarks);
	    // this message would be shown each time the browser is opened,
	    // which would create a lot of noise; commented, but we leave it
	    // in here since it might be useful for debugging purposes
	    //pdgui.post("loaded bookmarks: "+toc_config);
	}
    } catch (err) {
	pdgui.post("error reading bookmarks: "+toc_config, "error");
	pdgui.post("format error (expected an array, got " +
		   (typeof bookmarks === "object" ? "an " :
		    typeof bookmarks === "undefined" ? "" : "a ") +
		   typeof bookmarks + ")");
    }
}

function toc_add_bookmark(id, title, descr)
{
    if (toc_bookmarks() < 0) {
	toc_add_bookmarks();
    }
250
    toc[toc.length] = descr ? {
251
252
253
	id: id,
	title: title,
	description: descr
254
255
256
    } : {
	id: id,
	title: title
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
    };
    pdgui.post("add bookmark: "+title+" ("+id+")");
    toc_save();
}

function toc_delete_bookmark(id, title)
{
    var i = toc_bookmarks();
    if (i >= 0) {
	var l = toc.length;
	while (i < l &&
	       (toc[i].id !== id || toc[i].title !== title)) {
	    i++;
	}
	if (i < l) {
	    toc.splice(i, 1);
	    pdgui.post("delete bookmark: "+title+" ("+id+")");
	} else {
	    // not found, we're done
	    return;
	}
    } else {
	// no bookmarks, we're done
	return;
    }
    if (toc_bookmarks() == toc.length-1) {
	// empty bookmark section, remove the section title
	toc.pop();
    }
    toc_save();
}

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
function toc_is_bookmarked(id)
{
    var i = toc_bookmarks();
    if (i >= 0) {
	var l = toc.length;
	while (i < l && toc[i].id !== id) {
	    i++;
	}
	return i<l;
    } else {
	// no bookmarks
	return false;
    }
}

304
305
// Stop-gap translator
function translate_form() {
306
307
308
    var elements = document.querySelectorAll("[data-i18n]"),
        data,
        i;
309
    for (i = 0; i < elements.length; i++) {
310
        data = elements[i].dataset.i18n;
311
312
313
314
315
316
317
318
        if (data.slice(0,7) === "[title]") {
            elements[i].title = l(data.slice(7));
        } else {
            elements[i].textContent = l(data);
        }
    }
}

319
320
321
322
323
function click_toc(dir) {
    document.getElementById("search_text").value = dir;
    doc_search();
}

324
325
var current_dir;

326
327
328
329
330
331
332
333
function canonical_path(dir)
{
    // normalize
    dir = path.normalize(dir);
    // get rid of Windows' '\', '/' works just as well and is more portable
    return pdgui.defunkify_windows_path(dir);
}

334
335
336
337
338
339
340
341
342
343
function check_dir(dirname)
{
    var absname = dirname;
    if (!path.isAbsolute(dirname)) {
	// A relative path is taken relative to libdir, so that, e.g., doc/*
	// and extra/* can be used to access documentation in the doc and
	// extra hierarchies.
	absname = path.join(pdgui.get_lib_dir(), dirname);
    }
    try {
344
345
	if (fs.lstatSync(absname).isDirectory())
	    return canonical_path(absname);
346
347
348
349
350
    } catch (err) {
    }
    return null;
}

351
352
353
354
355
356
357
358
359
function toc_bookmark_update(dir)
{
    var bookmark = document.getElementById("bookmark_indicator");
    var rel = canonical_path(path.relative(pdgui.get_lib_dir(), dir));
    dir = canonical_path(dir);
    var id = dir.length <= rel.length ? dir : rel;
    bookmark.src = toc_is_bookmarked(id) ? "bookmark2.svg" : "bookmark.svg";
}

360
361
362
363
364
function toc_bookmark_status(enabled) {
    document.getElementById("bookmark_indicator").style.
	setProperty("opacity", enabled?"1.0":"0.5");
}

365
366
367
368
369
370
function display_toc() {
    var results_elem = document.getElementById("results"),
        div,
        a,
        header,
        text_node;
371
372
    // reset current_dir to doc
    current_dir = path.join(pdgui.get_lib_dir(), "doc");
373
    toc_bookmark_update(current_dir);
374
    toc_bookmark_status(false);
375
    div = document.createElement("div");
376
    toc.forEach(function(doc, i, a) {
377
	if (doc.id) {
378
	    try {
379
		fs.accessSync(check_dir(doc.id), fs.F_OK);
380
		a = document.createElement("a");
381
382
		// We need to properly stringify click_toc's argument here.
		a.href = "javascript: click_toc(" + JSON.stringify(doc.id) + ");";
383
384
385
386
387
388
389
390
391
392
393
394
		a.textContent = doc.title;
		// set title to path for tooltip
		a.title = doc.id;
		header = document.createElement("h3");
		header.appendChild(a);
		div.appendChild(header);
		if (doc.description) {
		    text_node = document.createTextNode(doc.description);
		    div.appendChild(text_node);
		}
	    } catch (e) {
		// entry doesn't exist, ignore it
395
	    }
396
397
398
399
400
401
402
	} else {
	    // make a session title
            header = document.createElement("h2");
            text_node = document.createTextNode(doc.title);
            header.appendChild(text_node);
            div.appendChild(header);
	}
403
404
405
	if (div.hasChildNodes()) {
            results_elem.appendChild(div);
	}
406
407
408
    });
}

409
410
function finish_build(idx) {
    index = idx;
411
    document.getElementById("search_text").disabled = false;
412
    clear_results();
413
    toc_load();
414
    display_toc();
415
}
416

417
418
419
420
421
422
423
424
425
426
function clear_results() {
    document.getElementById("results").innerHTML = "";
}

function display_directory_callback(err, files) {
    var doc, doc_path;
    if (err) {
        pdgui.post("Search Engine: " + err);
    } else {
        files.forEach(function (f, i, a) {
427
428
429
430
            if (filetypes.indexOf(path.extname(f)) !== -1) {
                doc_path = path.join(current_dir, f);
                doc = index.documentStore.getDoc(doc_path) || {
                    id: doc_path,
431
                    title: path.basename(f, ".pd"),
432
433
434
435
                    description: null
                };
                display_doc(doc);
            }
436
437
438
439
440
441
        });
    }
}

function display_directory(dir) {
    current_dir = dir;
442
    var is_doc = current_dir === path.join(pdgui.get_lib_dir(), "doc");
443
444
    clear_results();
    fs.readdir(dir, display_directory_callback);
445
    toc_bookmark_update(dir);
446
    toc_bookmark_status(!is_doc);
447
448
}

449
function file_browser_click() {
450
451
452
453
    // open the file browser on whatever directory we visited last, or the
    // doc directory by default
    document.getElementById("file_browser").setAttribute("nwworkingdir",
        current_dir);
454
455
456
    document.getElementById("file_browser").click();
}

457
458
459
460
function bookmark_indicator_click() {
    toggle_bookmark(current_dir);
}

461
462
function file_browser_callback(elem) {
    var doc = elem.value;
463
    if (doc !== "") {
464
465
466
467
468
469
470
471
472
473
        var defunkify = pdgui.defunkify_windows_path;
        var dir = path.dirname(doc);
        pdgui.doc_open(defunkify(dir), defunkify(path.basename(doc)));
        display_directory(defunkify(dir));
        // update the search field accordingly; use a relative path if that
        // makes sense, and canonicalize
        var rel = canonical_path(path.relative(pdgui.get_lib_dir(), dir));
        dir = canonical_path(dir);
        dir = dir.length <= rel.length ? dir : rel;
        document.getElementById("search_text").value = dir;
474
    }
475
476
}

477
478
479
480
481
482
483
484
function console_unwrap_tag(console_elem, tag_name) {
    var b = console_elem.getElementsByTagName(tag_name),
        parent_elem;
    while (b.length) {
        parent_elem = b[0].parentNode;
        while(b[0].firstChild) {
            parent_elem.insertBefore(b[0].firstChild, b[0]);
        }
485
        parent_elem.removeChild(b[0]);
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
        parent_elem.normalize();
    }
}

function console_find_text(elem, evt, callback) {
    var console_text = document.getElementById("results"),
        wrap_tag = "mark",
        wrapper_count;
    window.setTimeout(function () {
        console_unwrap_tag(console_text, wrap_tag);
        // Check after the event if the value is empty
        if (elem.value === undefined || elem.value === "") {
            // Todo: use class instead of style here
            elem.style.setProperty("background", "white");
        } else {
            window.findAndReplaceDOMText(console_text, {
                //preset: "prose",
503
                find: elem.value,
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
                wrap: wrap_tag
            });
            // The searchAndReplace API is so bad you can't even know how
            // many matches there were without traversing the DOM and
            // counting the wrappers!
            wrapper_count = console_text.getElementsByTagName(wrap_tag).length;
            // Todo: use class instead of style here...
            if (wrapper_count < 1) {
                elem.style.setProperty("background", "red");
            } else {
                elem.style.setProperty("background", "white");
            }
        }
        if (callback) {
            callback();
        }
    }, 0);
}

// start at top and highlight the first result after a search
function console_find_callback() {
    var highlight_checkbox = document.getElementById("console_find_highlight");
    console_find_highlight_all(highlight_checkbox);
    console_find_traverse.set_index(0);
    console_find_traverse.next();
}

function console_find_keypress(elem, e) {
    console_find_text(elem, e, console_find_callback);
}

function console_find_highlight_all(elem) {
    var matches,
        highlight_tag = "console_find_highlighted",
        state = elem.checked,
        i, len;
    matches = document.getElementById("results")
        .getElementsByClassName(highlight_tag);
    // remember-- matches is a _live_ collection, not an array.
    // If you remove the highlight_tag from an element, it is
    // automatically removed from the collection. I cannot yet
    // see a single benefit to this behavior-- here, it means
    // we must decrement i to keep from skipping over every
    // other element... :(
    for (i = matches.length - 1; i >= 0; i--) {
        matches[i].classList.remove(highlight_tag);
    }
    if (state) {
        matches = document.getElementById("results").getElementsByTagName("mark");
        for (i = 0; i < matches.length; i++) {
            matches[i].classList.add(highlight_tag);
        }
    }
}

var console_find_traverse = (function() {
    var count = 0,
        wrap_tag = "mark";
    return {
        next: function() {
            var i, last, next,
                console_text = document.getElementById("results"),
                elements = console_text.getElementsByTagName(wrap_tag);
            if (elements.length > 0) {
                i = count % elements.length;
                elements[i].classList.add("console_find_current");
                if (elements.length > 1) {
                    last = i === 0 ? elements.length - 1 : i - 1;
                    next = (i + 1) % elements.length;
                    elements[last].classList.remove("console_find_current");
                    elements[next].classList.remove("console_find_current");
                }
                // adjust the scrollbar to make sure the element is visible,
                // but only if necessary.
                // I don't think this is available on all browsers...
                elements[i].scrollIntoViewIfNeeded();
                count++;
            }
        },
        set_index: function(c) {
            count = c;
        }
    }
}());

function console_find_keydown(elem, evt) {
    if (evt.keyCode === 13) {
        console_find_traverse.next();
        evt.stopPropagation();
        evt.preventDefault();
        return false;
    } else if (evt.keyCode === 27) { // escape

    } else if (evt.keyCode === 8 || // backspace or delete
               evt.keyCode === 46) {
        console_find_text(elem, evt, console_find_callback);
    }
}

function find_bar_shortcut(evt) {
    var osx = process.platform === "darwin",
    modifier = osx ? "metaKey" : "ctrlKey";
    return (evt.keyCode === 70 && evt[modifier]) // <ctrl-f>
}

609
610
611
612
613
614
function window_close_shortcut(evt) {
    var osx = process.platform === "darwin",
    modifier = osx ? "metaKey" : "ctrlKey";
    return (evt.keyCode === 87 && evt[modifier]) // <ctrl-w>
}

615
616
617
618
619
620
function bookmark_shortcut(evt) {
    var osx = process.platform === "darwin",
    modifier = osx ? "metaKey" : "ctrlKey";
    return (evt.keyCode === 68 && evt[modifier]) // <ctrl-d> adds, <ctrl-shift-d> deletes a bookmark
}

621
622
623
624
625
626
function toggle_find_bar() {
    // this is copied from index.js m.edit.find...
    var find_div = document.getElementById("console_find"),
        find_bar_text = document.getElementById("console_find_text"),
        state = find_div.style.getPropertyValue("display");
    if (state !== "inline") {
627
        find_div.style.setProperty("height", "1.2em");
628
        find_div.style.setProperty("display", "inline");
629
630
        find_div.style.setProperty("padding-left", "3px");
        find_div.style.setProperty("padding-bottom", "9px");
631
632
633
634
635
636
        window.setTimeout(function() {
                find_bar_text.focus();
                find_bar_text.select();
            }, 0);
    } else {
        find_div.style.setProperty("display", "none");
637
        find_div.style.setProperty("height", "0em");
638
639
640
641
642
643
        // Blur focus so that the console_find keypress doesn't
        // receive our shortcut key
        find_div.blur();
    }
}

644
645
646
// Adds (or deletes, if del is true) a bookmark to the toc.
function do_bookmark(dirname, del)
{
647
648
649
650
651
652
653
654
    if (current_dir === path.join(pdgui.get_lib_dir(), "doc")) {
	/* We don't want to bookmark the doc directory. There's nothing
	   interesting to see there anyway, and, since the toc also lives
	   there, just bailing out at this point we prevent an interesting
	   race condition which arises if we try to update the toc while we're
	   displaying it. */
	return;
    }
655
656
657
658
659
    /* id (dirname) for the bookmark. We take this relative to the libdir if
       the relative designation is shorter than the absolute path, which gives
       prettier ids for documents in the doc and extra hierarchies and
       siblings. Note that these ids only ever appear in the toc, never in
       documents in the search database. */
660
661
662
    var relname = canonical_path(path.relative(pdgui.get_lib_dir(), dirname));
    dirname = canonical_path(dirname);
    var id = dirname.length <= relname.length ? dirname : relname;
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
    // Default name for the bookmark.
    var name = path.basename(dirname);
    // Let's check whether the directory contains a meta file from which we
    // may get name and description of the external.
    var meta = path.join(dirname, name+"-meta.pd");
    var meta_name, meta_descr;
    try {
        var data = fs.readFileSync(meta, 'utf8').replace("\n", " ");
	meta_name = data.match
	(/#X text \-?[0-9]+ \-?[0-9]+ NAME ([\s\S]*?);/i);
	meta_descr = data.match
	(/#X text \-?[0-9]+ \-?[0-9]+ DESCRIPTION ([\s\S]*?);/i);
	meta_name = meta_name && meta_name.length > 1 ?
	    meta_name[1].trim() : null;
	meta_descr = meta_descr && meta_descr.length > 1 ?
	    meta_descr[1].trim() : null;
	// Remove the Pd escapes for commas
	meta_descr = meta_descr ?
	    meta_descr.replace(" \\,", ",") : null;
    } catch (err) {
        // ignore
    }
    name = meta_name ? meta_name : name;
    if (del)
	toc_delete_bookmark(id, name);
    else
	toc_add_bookmark(id, name, meta_descr);
690
    toc_bookmark_update(dirname);
691
692
693
694
695
696
}

// Toggle bookmark for the given directory. This is invoked by clicking on
// the bookmark indicator to the right of the search field. 
function toggle_bookmark(dir)
{
697
698
    var rel = canonical_path(path.relative(pdgui.get_lib_dir(), dir));
    dir = canonical_path(dir);
699
700
    var id = dir.length <= rel.length ? dir : rel;
    do_bookmark(dir, toc_is_bookmarked(id));
701
702
}

703
704
705
706
707
708
709
710
711
function is_special_key(key)
{
    // determine all special keys which should be handled on the spot and
    // *not* be forwarded to the search text field
    return key === 9 // tab key
        || (key >= 16 && key <= 18) // modifier (shift, ctrl, alt)
        || (key >= 33 && key <= 40); // cursor keys
}

712
function add_events() {
713
714
715
716
717
718
    // closing the Window
    nw.Window.get().on("close", function() {
        pdgui.remove_dialogwin("search");
        nw.Window.get().close(true);
    });

719
720
721
722
723
724
725
726
    // Find bar
    var find_bar = document.getElementById("console_find_text");
    find_bar.placeholder = "Search in Console";
    find_bar.addEventListener("keydown",
        function(evt) {
            if (find_bar_shortcut(evt)) {
                toggle_find_bar();
                evt.stopPropagation();
727
728
729
730
            } else if (window_close_shortcut(evt)) {
                evt.stopPropagation();
                pdgui.remove_dialogwin("search");
                nw.Window.get().close(true);
731
732
733
734
735
736
737
738
739
740
741
742
            } else {
                evt.stopPropagation();
                return console_find_keydown(this, evt);
            }
        }, false
    );
    find_bar.addEventListener("keypress",
        function(evt) {
            console_find_keypress(this, evt);
        }, false
    );

743
744
745
746
747
748
749
    document.getElementById("file_browser_button").addEventListener("click",
        function(evt) {
            if (evt.currentTarget === document.activeElement) {
                file_browser_click();
            }
    });

750
751
752
753
754
755
756
    document.getElementById("bookmark_indicator").addEventListener("click",
        function(evt) {
            if (evt.currentTarget === document.activeElement) {
                bookmark_indicator_click();
            }
    });

757
758
    // Keydown in the document
    document.body.addEventListener("keydown", function(evt) {
759
        var input_elem = document.getElementById("search_text"),
760
761
            button_elem = document.getElementById("file_browser_button"),
            button_elem2 = document.getElementById("bookmark_indicator");
762
763
        if (find_bar_shortcut(evt)) {
            toggle_find_bar();
764
765
        } else if ((evt.target === button_elem ||
		    evt.target === button_elem2) &&
766
                   evt.keyCode === 10 || evt.keyCode === 13) {
767
        } else if (is_special_key(evt.keyCode)) {
768
769
        } else if (evt.target !== input_elem) {
            input_elem.focus();
770
771
772
773
774
775
776
        } else if (bookmark_shortcut(evt)) {
	    // We assume here that current_dir is set and points to the
	    // directory to be bookmarked.
	    if (current_dir) {
                evt.stopPropagation();
		do_bookmark(current_dir, evt.shiftKey);
	    }
777
778
779
780
        } else if (window_close_shortcut(evt)) {
                evt.stopPropagation();
                pdgui.remove_dialogwin("search");
                nw.Window.get().close(true);
781
782
783
784
        } else {
            // If we want to trigger a search on each keystroke we can do it
            // here.
        }
785

786
    });
787
788
789
790
    document.getElementById("search_text").addEventListener("keyup",
        function() {
            doc_search();
    });
791
792
793
794
    document.getElementById("search_text").addEventListener("search",
        function() {
            doc_search();
    });
795
796
}

797
function register_window_id(id, attrs) {
798
    var doc_path = path.join(pdgui.get_lib_dir(), "doc");
799
    translate_form();
800
801
802
    // Translate the placeholder for the search input:
    document.getElementById("search_text").placeholder =
        l("search.search_placeholder");
803
    add_events();
804
805
    // set file types for the file dialog
    document.getElementById("file_browser").accept = filetypes.join(",");
806
    document.getElementById("results").textContent = l("search.building_index");
807
    document.getElementById("search_text").disabled = true;
808
    document.getElementById("file_browser").setAttribute("nwworkingdir",
809
        doc_path); // Probably need a doc getter in pdgui
810
    pdgui.build_index(finish_build);
811
812
}

813
function display_no_results() {
814
    document.getElementById("results").textContent = l("search.no_results");
815
    toc_bookmark_update(current_dir);
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
}

function display_doc(doc) {
    var div = document.createElement("div"),
        a = document.createElement("a"),
        results_elem = document.getElementById("results"),
        header,
        text_node;
    a.href = "javascript: pdgui.doc_open('" +
         pdgui.defunkify_windows_path(path.dirname(doc.id)) + "', '" +
         pdgui.defunkify_windows_path(path.basename(doc.id)) + "');"
    a.textContent = doc.title;
    // set title to path for tooltip
    a.title = doc.id;
    header = document.createElement("h3");
    header.appendChild(a);
    div.appendChild(header);
833
834
835
836
    if (doc.description) {
	text_node = document.createTextNode(doc.description);
	div.appendChild(text_node);
    }
837
838
839
840
    if (doc.related_objects) {
        text_node = document.createTextNode("\n Related objects: " + doc.related_objects.toString());
        div.appendChild(text_node);
    }
841
    results_elem.appendChild(div);
842
    toc_bookmark_update(current_dir);
843
844
}

845
846
847
848
849
function doc_search() {
    var text_elem = document.getElementById("search_text"),
        search_text = text_elem.value,
        results,
        doc,
850
        i;
851
852
    // reset current_dir to doc
    current_dir = path.join(pdgui.get_lib_dir(), "doc");
853
854
855
856
857
858
    // if the search term is empty then just redisplay the toc
    if (!search_text) {
        clear_results();
        display_toc();
        return;
    }
859
    // if the search term is a directory then short circuit
860
    // the search and just list the docs in that directory
861
862
863
    var dirname = check_dir(search_text);
    if (dirname) {
        display_directory(dirname);
864
865
866
        return;
    }
    clear_results();
867
    text_elem.blur();
868
    toc_bookmark_status(false);
869
870
871
872
    results = index.search(search_text, {
        fields: {
            title: {boost: 64, expand: true},
            keywords: {boost: 8, expand: true},
873
874
            description: {boost: 2},
            related_objects: {boost: 1}
875
876
        }
    });
877
878
879
880
881
882
883
    // Increase priority of pd-l2ork docs over externals docs
    for (i = 0; i < results.length; i++) {
        if (results[i].ref.match(/.*\/pd-l2ork\/doc\/.*/) != null) {
            results[i].score *= 5;
        }
    }
    results.sort(function(a, b) { return b.score - a.score; });
884
885
    for (i = 0; i < results.length; i++) {
        doc = index.documentStore.getDoc(results[i].ref);
886
        display_doc(doc);
887
888
    }
    if (results.length === 0) {
889
        display_no_results();
890
891
892
893
    }
}
    </script>
  </head>
894
  <body class="search_body">
895
    <input type="file" id="file_browser" style="display: none;" onchange="file_browser_callback(this);"></input>
896
897
898
899
900
901
902
903
904
905
906
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
         width="220" height="50" viewBox="0 0 140 31.81">
      <g stroke-linecap="square" fill="none" stroke-width="3">
        <path d="M3,4 a6.2,4 0 1,1 0,7 m-0.3,-7.4 0,17" stroke="#090"/>
        <path d="M14,13 a4,7 0 1,0 8,0 m0,2 0,5" stroke="#c0c"/>
        <path d="M27,20 s 0 -7 7 -7 m-7,0 0,7" stroke="#039"/>
        <path d="M39,20 s 0 -7 7 -7 m-7,0 0,7" stroke="#900"/>
        <path d="M60,4 a9,8 0 1,1 0,16 m0,-16 0,16" stroke="blue"/>
        <path d="M80,14 a-4,4.5 0 1,0 0,3 m0.5,3 0,-9" stroke="#f69"/>
        <path d="M88,20 l0,-11 m-3,2 6,0" stroke="#909"/>
        <path d="M102,14 a-4,4.5 0 1,0 0,3 m0.5,3 0,-9" stroke="#309"/>
907
908
      </g>
    </svg>
909
    <form id="search_form" action="javascript:void(0);">
910
911
912
      <input type="search"
             name="search_text"
             id="search_text"
913
914
             data-i18n="[title]search.search"
             autocomplete="off">
915
      <input type="image" style="vertical-align:middle;"
916
917
918
             src="folder.svg"
             id="file_browser_button"
             data-i18n="[title]search.browse">
919
920
921
922
      <input type="image" style="vertical-align:middle;"
             src="bookmark.svg"
             id="bookmark_indicator"
             data-i18n="[title]search.bookmark">
923
    </form>
924
925
    <div id="results">
    </div>
926
927
928
929
930
931
932
933
    <div id = "console_find" style="display:none;">
      <div>
        <label><input type="text"
                      id="console_find_text"
                      name="console_find_text"
                      defaultValue="Search in Console"
                      style="width:10em;"/>
        </label>
934
        <label style="margin-left: 7px;">Highlight All
935
936
937
               <input type="checkbox"
                      id="console_find_highlight"
                      name="console_find_highlight"
938
939
                      onchange="console_find_highlight_all(this);"
                      style="left: -3px;"/>
940
941
942
        </label>
      </div>
    </div>
943
944
  </body>
</html>