Newer
Older
Jonathan Wilkes
committed
<!DOCTYPE html>
<html>
<head>
Jonathan Wilkes
committed
<link id="page_style" rel="stylesheet"
type="text/css" href="css/default.css">
Jonathan Wilkes
committed
<title>Pd Search Engine</title>
<script type="text/javascript" src="./elasticlunr.js"></script>
Jonathan Wilkes
committed
<script type="text/javascript" src="./console_search.js"></script>
Jonathan Wilkes
committed
<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
Jonathan Wilkes
committed
var l = pdgui.get_local_string;
var index = elasticlunr();
pdgui.skin.apply(window);
Jonathan Wilkes
committed
index.addField("title");
index.addField("keywords");
index.addField("description");
//index.addField("body");
Jonathan Wilkes
committed
index.addField("path");
index.setRef("id");
var filetypes = [".pd", ".txt", ".htm", ".html", ".pdf"];
// Table of Contents to start with
Albert Gräf
committed
/* 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",
description: "HTML 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",
title: "Data Structures",
description: "custom data and 2d visualization"
},
{
id: "doc/5.reference",
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.
Albert Gräf
committed
{
title: "Stuff",
},
{
id: "doc/7.stuff/soundfile-tools",
Albert Gräf
committed
title: "Soundfile Tools",
description: "some standard audio transformations packaged to work with mono soundfiles"
},
{
id: "doc/7.stuff/synth",
Albert Gräf
committed
title: "Synth",
description: "polyphonic subtractive synthesizer example"
},
{
id: "doc/7.stuff/tools",
Albert Gräf
committed
title: "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"
Albert Gräf
committed
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
},
// 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)"
// },
Albert Gräf
committed
{
id: "extra/lyon",
title: "LyonPotpourri",
description: "Eric Lyon's external collection"
},
Jonathan Wilkes
committed
// Stop-gap translator
function translate_form() {
var elements = document.querySelectorAll("[data-i18n]"),
data,
i;
Jonathan Wilkes
committed
for (i = 0; i < elements.length; i++) {
Jonathan Wilkes
committed
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
Jonathan Wilkes
committed
.match(/#X text \-?[0-9]+ \-?[0-9]+ KEYWORDS ([\s\S]*?);/i),
Jonathan Wilkes
committed
desc = big_line
Jonathan Wilkes
committed
.match(/#X text \-?[0-9]+ \-?[0-9]+ DESCRIPTION ([\s\S]*?);/i);
Jonathan Wilkes
committed
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(" \\,", ",");
}
Jonathan Wilkes
committed
if (title.slice(-5) === "-help") {
title = title.slice(0, -5);
}
index.addDoc({
Jonathan Wilkes
committed
"id": filename,
Jonathan Wilkes
committed
"title": title,
"keywords": keywords,
Jonathan Wilkes
committed
"description": desc
//"body": big_line,
Jonathan Wilkes
committed
});
}
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);
}
Jonathan Wilkes
committed
}
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");
Albert Gräf
committed
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);
}
Albert Gräf
committed
} else {
// make a session title
header = document.createElement("h2");
text_node = document.createTextNode(doc.title);
header.appendChild(text_node);
div.appendChild(header);
}
results_elem.appendChild(div);
});
}
function finish_build() {
document.getElementById("search_text").disabled = false;
clear_results();
display_toc();
Jonathan Wilkes
committed
function build_index() {
var doc_path = path.join(pdgui.get_lib_dir(), "doc");
Jonathan Wilkes
committed
pdgui.post("doc path is " + doc_path);
dive(doc_path, read_file, finish_build);
Jonathan Wilkes
committed
}
Jonathan Wilkes
committed
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"),
description: null
};
display_doc(doc);
}
Jonathan Wilkes
committed
});
}
}
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)));
Jonathan Wilkes
committed
display_directory(pdgui.defunkify_windows_path(path.dirname(doc)));
}
Jonathan Wilkes
committed
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]);
}
Jonathan Wilkes
committed
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
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");
Jonathan Wilkes
committed
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");
Jonathan Wilkes
committed
// Blur focus so that the console_find keypress doesn't
// receive our shortcut key
find_div.blur();
}
}
function add_events() {
Jonathan Wilkes
committed
// closing the Window
nw.Window.get().on("close", function() {
pdgui.remove_dialogwin("search");
nw.Window.get().close(true);
});
Jonathan Wilkes
committed
// 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();
}
});
Jonathan Wilkes
committed
// Keydown in the document
document.body.addEventListener("keydown", function(evt) {
var input_elem = document.getElementById("search_text"),
button_elem = document.getElementById("file_browser_button");
Jonathan Wilkes
committed
if (find_bar_shortcut(evt)) {
toggle_find_bar();
} else if (evt.target === button_elem &&
evt.keyCode === 10 || evt.keyCode === 13) {
Jonathan Wilkes
committed
} 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();
});
Jonathan Wilkes
committed
}
Jonathan Wilkes
committed
function register_window_id(id, attrs) {
translate_form();
// Translate the placeholder for the search input:
document.getElementById("search_text").placeholder =
l("search.search_placeholder");
Jonathan Wilkes
committed
add_events();
// set file types for the file dialog
document.getElementById("file_browser").accept = filetypes.join(",");
document.getElementById("results").textContent = "Building index...";
Jonathan Wilkes
committed
document.getElementById("search_text").disabled = true;
document.getElementById("file_browser").setAttribute("nwworkingdir",
pdgui.get_gui_dir() + "/doc"); // Probably need a doc getter in pdgui
Jonathan Wilkes
committed
build_index();
}
Jonathan Wilkes
committed
function display_no_results() {
document.getElementById("results").textContent = "No results found.";
Jonathan Wilkes
committed
}
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);
}
Jonathan Wilkes
committed
results_elem.appendChild(div);
}
Jonathan Wilkes
committed
function doc_search() {
var text_elem = document.getElementById("search_text"),
search_text = text_elem.value,
results,
doc,
Jonathan Wilkes
committed
i;
// 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/") &&
Jonathan Wilkes
committed
search_text.indexOf(" ") === -1) {
display_directory(path.join(pdgui.get_lib_dir(), search_text));
return;
}
clear_results();
Jonathan Wilkes
committed
text_elem.blur();
results = index.search(search_text);
for (i = 0; i < results.length; i++) {
doc = index.documentStore.getDoc(results[i].ref);
Jonathan Wilkes
committed
display_doc(doc);
Jonathan Wilkes
committed
}
if (results.length === 0) {
Jonathan Wilkes
committed
display_no_results();
Jonathan Wilkes
committed
}
}
</script>
</head>
<body class="search_body">
<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"/>
Jonathan Wilkes
committed
</g>
</svg>
<form id="search_form" action="javascript:void(0);">
Jonathan Wilkes
committed
<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">
</form>
Jonathan Wilkes
committed
<div id="results">
</div>
Jonathan Wilkes
committed
<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>
Jonathan Wilkes
committed
</body>
</html>