Skip to content
Snippets Groups Projects
dialog_search.html 19.9 KiB
Newer Older
    <link id="page_style" rel="stylesheet"
          type="text/css" href="css/default.css">
    <title>Pd Search Engine</title>
    <script type="text/javascript" src="./elasticlunr.js"></script>
    <script type="text/javascript" src="./console_search.js"></script>
    <script>
"use strict";
var pdgui = require("./pdgui.js");
var fs = require("fs");
var path = require("path");
var dive = require("./dive.js"); // small module to recursively search dirs
var l = pdgui.get_local_string;

var index = elasticlunr();
index.addField("title");
index.addField("keywords");
index.addField("description");
var filetypes = [".pd", ".txt", ".htm", ".html", ".pdf"];

// Table of Contents to start with
/* 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. */
    // Original Pd documentation
    {
        id: "doc/1.manual",
        title: "Pd Manual",
    },
    {
        id: "doc/2.control.examples",
        title: "Control Tutorials",
        description: "the Pd language and editor"
    },
    {
        id: "doc/3.audio.examples",
        title: "Audio Tutorials",
        description: "manipulating audio in realtime"
        id: "doc/4.data.structures",
        description: "custom data and 2d visualization"
        title: "Internal Objects",
        description: "Purr Data's built-in classes"
    {
        id: "doc/6.externs",
        title: "Externals",
        description: "how to code control and signal objects in C"
    },
    // Section 7 of the vanilla Pd docs, uncomment these if needed.
    {
        id: "doc/7.stuff/soundfile-tools",
        description: "some standard audio transformations packaged to work with mono soundfiles"
    },
    {
        id: "doc/7.stuff/synth",
        description: "polyphonic subtractive synthesizer example"
    },
    {
        id: "doc/7.stuff/tools",
        description: "useful little helper patches"
    },
*/
    // Pd-L2Ork extras
    {
        id: "doc/4.data.structures/pd-l2ork/ds-tutorials",
        title: "Pd-l2ork Data Structures",
        description: "new and improved data structure visualizations"
    },
    // 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",
        description: "3D graphics with GEM"
    },
    {
        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",
    },
    {
        id: "extra/cyclone",
        title: "Cyclone",
        description: "library of clones of Max/MSP 4.x objects"
    },
    // Gem isn't currently running on OSX, so we don't list it here
//    {
//        id: "extra/Gem",
//        title: "GEM",
//        description: "Graphics Environment for Multimedia (OpenGL graphics in Pd)"
//    },
    {
        id: "extra/lyon",
        title: "LyonPotpourri",
        description: "Eric Lyon's external collection"
    },
// Stop-gap translator
function translate_form() {
    var elements = document.querySelectorAll("[data-i18n]"),
        data,
        i;
        data = elements[i].dataset.i18n;
        if (data.slice(0,7) === "[title]") {
            elements[i].title = l(data.slice(7));
        } else {
            elements[i].textContent = l(data);
        }
    }
}

function add_doc_to_index(filename, data) {
    var title = path.basename(filename, ".pd"),
        big_line = data.replace("\n", " "),
        keywords,
        desc;
        // We use [\s\S] to match across multiple lines...
        keywords = big_line
            .match(/#X text \-?[0-9]+ \-?[0-9]+ KEYWORDS ([\s\S]*?);/i),
            .match(/#X text \-?[0-9]+ \-?[0-9]+ DESCRIPTION ([\s\S]*?);/i);
        keywords = keywords && keywords.length > 1 ? keywords[1].trim() : null;
        desc = desc && desc.length > 1 ? desc[1].trim() : null;
        // Remove the Pd escapes for commas
        desc = desc ? desc.replace(" \\,", ",") : null;
        if (desc) {
            // format Pd's "comma atoms" as normal commas
            desc = desc.replace(" \\,", ",");
        }
    if (title.slice(-5) === "-help") {
        title = title.slice(0, -5);
    }
    index.addDoc({
function read_file(err, filename, stat) {
    if (!err) {
        if (filename.slice(-3) === ".pd") {
            fs.readFile(filename, { encoding: "utf8", flag: "r" },
                function(read_err, data) {
                    if (!read_err) {
                        add_doc_to_index(filename, data);
                    } else {
                        pdgui.post("err: " + read_err);
                    }
                }
            );
        }
    } else {
        pdgui.post("err: " + err);
    }
function click_toc(dir) {
    document.getElementById("search_text").value = dir;
    doc_search();
}

function display_toc() {
    var results_elem = document.getElementById("results"),
        div,
        a,
        header,
        text_node;
    toc.forEach(function(doc, i, a) {
        div = document.createElement("div");
	if (doc.id) {
            a = document.createElement("a");
            a.href = "javascript: click_toc('" + 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);
	    if (doc.description) {
		text_node = document.createTextNode(doc.description);
		div.appendChild(text_node);
	    }
	} else {
	    // make a session title
            header = document.createElement("h2");
            text_node = document.createTextNode(doc.title);
            header.appendChild(text_node);
            div.appendChild(header);
	}
function finish_build() {
    document.getElementById("search_text").disabled = false;
    var doc_path = path.join(pdgui.get_lib_dir(), "doc");
    dive(doc_path, read_file, finish_build);
function clear_results() {
    document.getElementById("results").innerHTML = "";
}

var current_dir;

function display_directory_callback(err, files) {
    var doc, doc_path;
    if (err) {
        pdgui.post("Search Engine: " + err);
    } else {
        files.forEach(function (f, i, a) {
            if (filetypes.indexOf(path.extname(f)) !== -1) {
                doc_path = path.join(current_dir, f);
                doc = index.documentStore.getDoc(doc_path) || {
                    id: doc_path,
                    title: path.basename(f, ".pd"),
        });
    }
}

function display_directory(dir) {
    current_dir = dir;
    clear_results();
    fs.readdir(dir, display_directory_callback);
}

function file_browser_click() {
    document.getElementById("file_browser").click();
}

function file_browser_callback(elem) {
    var doc = elem.value;
    //pdgui.post("file callback: file is " + elem.value);
    //pdgui.post("dir is " + pdgui.defunkify_windows_path(path.dirname(doc)));
    //pdgui.post("file is " + pdgui.defunkify_windows_path(path.basename(doc)));
    pdgui.doc_open(pdgui.defunkify_windows_path(path.dirname(doc)),
        pdgui.defunkify_windows_path(path.basename(doc)));
    display_directory(pdgui.defunkify_windows_path(path.dirname(doc)));
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]);
        }
        parent_elem.removeChild(b[0]);
        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",
                find: elem.value.toLowerCase(),
                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>
}

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") {
        find_div.style.setProperty("height", "1.4em");
        find_div.style.setProperty("display", "inline");
        window.setTimeout(function() {
                find_bar_text.focus();
                find_bar_text.select();
            }, 0);
    } else {
        find_div.style.setProperty("display", "none");
        find_div.style.setProperty("height", "0em");
        // Blur focus so that the console_find keypress doesn't
        // receive our shortcut key
        find_div.blur();
    }
}

function add_events() {
    // closing the Window
    nw.Window.get().on("close", function() {
        pdgui.remove_dialogwin("search");
        nw.Window.get().close(true);
    });

    // 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();
            } else {
                evt.stopPropagation();
                return console_find_keydown(this, evt);
            }
        }, false
    );
    find_bar.addEventListener("keypress",
        function(evt) {
            console_find_keypress(this, evt);
        }, false
    );

    document.getElementById("file_browser_button").addEventListener("click",
        function(evt) {
            if (evt.currentTarget === document.activeElement) {
                file_browser_click();
            }
    });

    // Keydown in the document
    document.body.addEventListener("keydown", function(evt) {
        var input_elem = document.getElementById("search_text"),
            button_elem = document.getElementById("file_browser_button");
        } else if (evt.target === button_elem &&
                   evt.keyCode === 10 || evt.keyCode === 13) {
        } else if (evt.target !== input_elem) {
            input_elem.focus();
        } else {
            // If we want to trigger a search on each keystroke we can do it
            // here.
        }
    });
    document.getElementById("search_text").addEventListener("search",
        function() {
            doc_search();
    });
function register_window_id(id, attrs) {
    translate_form();
    // Translate the placeholder for the search input:
    document.getElementById("search_text").placeholder =
        l("search.search_placeholder");
    // set file types for the file dialog
    document.getElementById("file_browser").accept = filetypes.join(",");
Albert Gräf's avatar
Albert Gräf committed
    document.getElementById("results").textContent = "Building index...";
    document.getElementById("search_text").disabled = true;
    document.getElementById("file_browser").setAttribute("nwworkingdir",
        pdgui.get_gui_dir() + "/doc"); // Probably need a doc getter in pdgui
Albert Gräf's avatar
Albert Gräf committed
    document.getElementById("results").textContent = "No results found.";
}

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);
    if (doc.description) {
	text_node = document.createTextNode(doc.description);
	div.appendChild(text_node);
    }
function doc_search() {
    var text_elem = document.getElementById("search_text"),
        search_text = text_elem.value,
        results,
        doc,
    // if the search term is empty then just redisplay the toc
    if (!search_text) {
        clear_results();
        display_toc();
        return;
    }
    // if the search term is doc/* or extra/* then short circuit
    // the search and just list the docs in that directory
    if ((search_text.slice(0, 4) === "doc/" ||
         search_text.slice(0, 6) === "extra/") &&
        search_text.indexOf(" ") === -1) {
        display_directory(path.join(pdgui.get_lib_dir(), search_text));
        return;
    }
    clear_results();
    text_elem.blur();
    results = index.search(search_text);
    for (i = 0; i < results.length; i++) {
        doc = index.documentStore.getDoc(results[i].ref);
    <input type="file" id="file_browser" style="display: none;" onchange="file_browser_callback(this);"></input>
    <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"/>
    <form id="search_form" action="javascript:void(0);">
      <input type="search"
             name="search_text"
             id="search_text"
             data-i18n="[title]search.search">
      <input type="image"
             src="folder.svg"
             id="file_browser_button"
             data-i18n="[title]search.browse">
    <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>
        <label>Highlight All
               <input type="checkbox"
                      id="console_find_highlight"
                      name="console_find_highlight"
                      onchange="console_find_highlight_all(this);"/>
        </label>
      </div>
    </div>