From e80874f5c742381c834772cc13ca8c2fa115a712 Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer Date: Sun, 11 Apr 2010 18:21:05 +0200 Subject: [PATCH] Docs: Add AJAX search field to the docs -) Add a search field to the toc pane in our docs. If the user types three letters or more, an AJAX request is sent to the server to perform a search. The AJAX part is implemented in JS, the search part itself is implemented in PHP on the server side, which wades through a pre-generated index file (pure text). -) The texi2html init file now has the ability to create our own search index file, currently in pure text (the search script will go through it line for line. -) The search box is shown via JavaScript only if the files are viewed over http. If the user has JavaScript disabled or if the files are viewed as static pages (and not over http), then no search box appears, since it would not work anyway. -) Also, don't show search box when AJAX initialization failed. -) Print nice message if search index can't be opened, don't fail with error -) Install and use language-dependent index files for all known languages -) If we don't have a texinfo index, don't load the JS and don't print out the search box. (Workaround: css_lines is called before init_out, so we need to do the check already in css_lines!) If the index would be empty, don't create an empty .idx file -) If AJAX works and a user presses enter, the same AJAX query is triggered as when entering some letters, and the results appear on the same page. However, if for some reason AJAX does not work (in particular, if the onSubmit action handler isn't called), then a search page is loaded with the same results as the AJAX query... --- Documentation/GNUmakefile | 18 ++++- Documentation/css/lilypond-mccarty.css | 25 ++++++ Documentation/lily_index_search.php | 62 +++++++++++++++ Documentation/lily_search.js | 101 +++++++++++++++++++++++++ Documentation/lilypond-texi2html.init | 79 +++++++++++++++++++ make/doc-i18n-root-rules.make | 1 + scripts/build/www_post.py | 2 +- 7 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 Documentation/lily_index_search.php create mode 100644 Documentation/lily_search.js diff --git a/Documentation/GNUmakefile b/Documentation/GNUmakefile index d1ffcf4e82..5a8cec7c06 100644 --- a/Documentation/GNUmakefile +++ b/Documentation/GNUmakefile @@ -91,9 +91,19 @@ include $(depth)/make/stepmake.make OUT_TXT_FILES = $(addprefix $(outdir)/, $(addsuffix .txt, $(README_TOP_FILES))) + ### Web site idiosyncrases $(XREF_MAPS_DIR)/web.xref-map: XREF_MAP_FLAGS += --split=node +### AJAX scripts +JS_FILES = $(call src-wildcard,*.js) +PHP_FILES = $(call src-wildcard,*.php) +EXTRA_DIST_FILES += $(JS_FILES) + +OUT_JS_FILES = $(JS_FILES:%.js=$(outdir)/%.js) +OUT_PHP_FILES = $(PHP_FILES:%.php=$(outdir)/%.php) + + ### bad hack for badly-integrated roadmap. $(outdir)/ROADMAP: @@ -168,7 +178,7 @@ local-clean: ifeq ($(out),www) local-WWW-1: $(OUT_TEXINFO_MANUALS) $(PDF_FILES) info -local-WWW-2: txt-to-html $(OUT_HTML_FILES) $(DEEP_HTML_FILES) $(source-links) $(OMF_FILES) +local-WWW-2: txt-to-html $(OUT_HTML_FILES) $(DEEP_HTML_FILES) $(OUT_JS_FILES) $(OUT_PHP_FILES) $(source-links) $(OMF_FILES) endif @@ -233,6 +243,12 @@ $(outdir)/snippets.texi: $(GENERATED_ITELY_FILES) $(SNIPPET_LY_FILES) $(outdir)/%.bib: %.bib ln -f $< $@ +$(outdir)/%.js: %.js + ln -f $< $@ + +$(outdir)/%.php: %.php + ln -f $< $@ + ## notation.texi deps $(top-build-dir)/mf/$(outconfbase)/feta16list.ly: $(MAKE) -C $(top-src-dir)/mf diff --git a/Documentation/css/lilypond-mccarty.css b/Documentation/css/lilypond-mccarty.css index 4ccf54f206..7bafd33101 100644 --- a/Documentation/css/lilypond-mccarty.css +++ b/Documentation/css/lilypond-mccarty.css @@ -414,6 +414,31 @@ table.menu { .nav_table { display: none; } } +/***********************************************************/ +/* FORMATTING of AJAX SEARCH BOX */ +/***********************************************************/ + +div#search { + border: none; + border-bottom: 1pt solid #C5972C; + background: #E8E3AC; + padding-left: 3px; + padding-top: 2px; + padding-bottom: 1px; +} +div#search p, div#search form { + padding: 0; + margin: 0; +} +#search_results { + font-size: 0.75em; + padding: 0; + margin: 0; + display: none; +} +#search_results table { + width: 100%; +} /***********************************************************/ /* OTHER */ diff --git a/Documentation/lily_index_search.php b/Documentation/lily_index_search.php new file mode 100644 index 0000000000..f1b1e17b29 --- /dev/null +++ b/Documentation/lily_index_search.php @@ -0,0 +1,62 @@ +"en", "de"=>"de", "nl"=>"nl", "jp"=>"jp", "hu"=>"hu", "fr"=>"fr", ""=>"en"); + $manuals = array ("essay"=>"essay", "extending"=>"extending", "learning"=>"learning", "notation"=>"notation", "usage"=>"usage"); + + $lang = $languages[$_REQUEST['lang']]; + $man = $manuals[$_REQUEST['manual']]; + if (!$man) { + echo "

Invalid manual " . $_REQUEST['lang'] . "

"; + exit (); + } + $bigpage = ($_REQUEST['bigpage'] == "1"); + $search_string = $_REQUEST['q']; + // If enter was pressed, browsers will use the returned HTML for a complete page! + $form_submitted = $_REQUEST['form_submitted']; + + + $relpath = ""; + if ($form_submitted) { + if (! $bigpage) { + $relpath = "$man/"; + } + echo "\n"; + } + + $filename = "./$man"; + if ($bigpage) { $filename .= "-big-page"; } + $filename .= ".$lang.idx"; + + $found = 0; + $file = @fopen($filename, "r"); + if ($file ) { + while ( (($line=fgets($file)) !== false) ) { + $line = rtrim($line); + $entries = split ("\t", $line); + if (stripos ($entries[0], $search_string) !== false) { + if ($found == 0) { + echo "

Search results for "".htmlentities($search_string, ENT_QUOTES)."":
\n"; + echo "\n"; + } else if ($found > 50) { + echo "\n"; + break; + } + // format the entry and print it out + echo "\n"; + echo " \n"; + $found++; + } + } + if ($found > 0) { + echo "
Too many hits, displaying only 50 results
$entries[1]$entries[3]
\n"; + } else { + echo "No results found in the index.\n"; + } + echo "

"; + fclose($file); + } else { + echo "

Unable to open search index $filename

"; + } + if ($form_submitted) { + echo "\n"; + } +?> \ No newline at end of file diff --git a/Documentation/lily_search.js b/Documentation/lily_search.js new file mode 100644 index 0000000000..09516b306b --- /dev/null +++ b/Documentation/lily_search.js @@ -0,0 +1,101 @@ +var resObject = null; +var useAjax = (document.location.protocol.toLowerCase() == 'http:'); +var isLocal = !useAjax; + +var previous_search = ""; + +function erzXMLHttpRequestObject () +{ + var resObject = null; + try { + resObject = new XMLHttpRequest (); + } + catch (Error) { + try { + resObject = new ActiveXObject ("Microsoft.XMLHTTP"); + } + catch (Error) { + try { + resObject = new ActiveXObject ("MSXML2.XMLHTTP"); + } + catch (Error) { + alert ("Unable to create XMLHttpRequest object for the search function!"); + useAjax = false; + } + } + } + return resObject; +} + +function searchResult (language, manual, bigpage) +{ + search_string = this.document.search_form.q.value; + if (useAjax && previous_search != search_string) { + if (useAjax && search_string.length >= 3) { + var reldir = ""; + if (bigpage == 0) { + reldir = "../" + } + resObject.open ('get', reldir + 'lily_index_search.php?lang=' + escape(language) + '&manual=' + escape(manual) + '&bigpage=' + bigpage + '&q=' + escape(search_string), true); + resObject.onreadystatechange = handleResponse; + resObject.send (null); + } else { + clearResults (); + } + previous_search = search_string; + } +} + +function result_field () +{ + return document.getElementById ('search_results'); +} +function assignResults (results) +{ + field = result_field (); + field.innerHTML = resObject.responseText; + field.style.display = 'block'; +} + +function handleResponse () +{ + if (resObject.readyState == 4 ) { + assignResults (resObject.responseText); + } +} + +function clearResults () +{ + field = result_field (); + field.innerHTML = 0; + field.style.display = 'none'; +} + + +function print_search_field (language, manual, bigpage) +{ + if (useAjax) { + // If the user presses enter and submits the form, also call the search + // script to print out the results in a separate page + search_call = "searchResult('" + language + "', '" + manual + "', " + bigpage + ")"; + var reldir = ""; + if (bigpage == 0) { + reldir = "../" + } + search_script = reldir + 'lily_index_search.php'; + document.write("
"); + document.write("
"); + document.write(""); + document.write(""); + document.write(""); + document.write(""); + document.write("

Search: "); + document.write("

"); + document.write("
"); + document.write("
"); + document.write("
"); + } +} +if (useAjax) { + resObject = erzXMLHttpRequestObject (); +} \ No newline at end of file diff --git a/Documentation/lilypond-texi2html.init b/Documentation/lilypond-texi2html.init index 6fcaafc5f4..2ea8175c47 100644 --- a/Documentation/lilypond-texi2html.init +++ b/Documentation/lilypond-texi2html.init @@ -583,9 +583,14 @@ $Texi2HTML::Config::USE_REL_REV = 1; $Texi2HTML::Config::SPLIT_INDEX = 0; $Texi2HTML::Config::SEPARATED_FOOTNOTES = 0; # Print footnotes on same page, not separated +my $bigpage = 0; +my $have_index_entries = 0; if ($Texi2HTML::Config::SPLIT eq 'section' or $Texi2HTML::Config::SPLIT eq 'node') { $Texi2HTML::Config::element_file_name = \&lilypond_element_file_name; + $bigpage = 0; +} else { + $bigpage = 1; } $Texi2HTML::Config::anchor = \&lilypond_anchor; @@ -793,6 +798,17 @@ sub lilypond_css_lines ($$) # docs still need this? -gp $Texi2HTML::THISDOC{'CSS_LINES'} .= "\n"; + + # Add the JavaScript file only if we have an index. + # Unfortunately, init_out is called after css_lines, so as a workaround + # here we have to check again for the existence of an index... + my $ix = main::get_index ("cp"); + $have_index_entries = (scalar $ix > 0); + my $reldir = ""; + $reldir = "../" unless $bigpage; + if ($have_index_entries) { + $Texi2HTML::THISDOC{'CSS_LINES'} .= "\n"; + } } @@ -1359,6 +1375,14 @@ sub lilypond_print_toc_div ($$) &ly_get_string ('Back to Documentation Index') . "

\n"; + # AJAX search box, written in JavaScript + if ($have_index_entries) { + my ($docu_dir, $docu_name) = split_texi_filename ($Texi2HTML::THISDOC{'input_file_name'}); + print $fh '\n"; + } + print $fh '

' . &$anchor('', $Texi2HTML::HREF{'Top'}, $topname, @@ -1781,6 +1805,61 @@ sub makeinfo_like_paragraph ($$$$$$$$$$$$$) } +############################################################################# +### WRITING OUT THE INDEX FOR THE AJAX SEARCH FIELD +############################################################################# + +$Texi2HTML::Config::IDX_SUMMARY = 1; +$Texi2HTML::Config::init_out = \&lilypond_init_out; +$Texi2HTML::Config::finish_out = \&lilypond_finish_out; +$Texi2HTML::Config::index_summary_file_entry = \&lilypond_index_summary_file_entry; + +my @index_entries; + +sub lilypond_init_out () +{ + t2h_default_init_out (); + # Check whether we have an index at all! If not -> don't print out a search box! + my $ix = main::get_index ("cp"); + $have_index_entries = (scalar $ix > 0); +} + +sub lilypond_index_summary_file_entry ($$$$$$$$$) +{ + my $index_name = shift; + my $entry_text = shift; + my $entry_reference = shift; + my $formatted_entry = shift; + my $texi_entry = shift; + my $entry_element_reference = shift; + my $entry_element_header = shift; + my $is_printed = shift; + my $manual_name = shift; + + if ($index_name eq "cp") + { + # The entries in the index file have the form: + # SearchText \t FormattedText \t EntryURL \t SectionText \t SectionURL + push @index_entries, "$entry_text\t$formatted_entry\t$entry_reference\t$entry_element_header\t$entry_element_reference\n"; + } +} + +sub lilypond_finish_out () +{ + if (((scalar @index_entries) > 0) and $have_index_entries) { + my $lang = $Texi2HTML::THISDOC{current_lang}; + my $big = ""; + $big = "-big-page" if $bigpage; + my ($docu_dir, $docu_name) = split_texi_filename ($Texi2HTML::THISDOC{'input_file_name'}); + my $idx_file = "$docu_dir/$docu_name$big.$lang.idx"; + open IDXOUTFILE, ">:utf8", $idx_file; + print IDXOUTFILE @index_entries; + close IDXOUTFILE; + } +} + + + ############################################################################# ### OTHER SETTINGS ############################################################################# diff --git a/make/doc-i18n-root-rules.make b/make/doc-i18n-root-rules.make index 13d3618fa4..2d9dec0304 100644 --- a/make/doc-i18n-root-rules.make +++ b/make/doc-i18n-root-rules.make @@ -38,6 +38,7 @@ $(outdir)/pictures: $(TRANSLATION_LILY_IMAGES): $(MASTER_TEXI_FILES) find $(outdir) \( -name 'lily-*.png' -o -name 'lily-*.ly' \) | sed 's!$(outdir)/!!g' | xargs $(buildscript-dir)/mass-link hard $(outdir) $(top-build-dir)/Documentation/$(outdir) + find $(outdir) \( -name '*.??.idx' \) | sed 's!$(outdir)/!!g' | xargs $(buildscript-dir)/mass-link hard $(outdir) $(top-build-dir)/Documentation/$(outdir) touch $@ $(outdir)/lilypond-%.info: $(outdir)/%.texi $(outdir)/$(INFO_IMAGES_DIR).info-images-dir-dep $(outdir)/version.itexi $(outdir)/weblinks.itexi diff --git a/scripts/build/www_post.py b/scripts/build/www_post.py index a9ab88cd3e..17af63109e 100644 --- a/scripts/build/www_post.py +++ b/scripts/build/www_post.py @@ -41,7 +41,7 @@ dirs, symlinks, files = mirrortree.walk_tree ( exclude_dirs = '(^|/)((' + \ r'po|xref-maps|out|out-test|out-cov|.*?[.]t2d|\w*?-root)|^Documentation/(' + \ '|'.join ([l.code for l in langdefs.LANGUAGES]) + '))(/|$)', - find_files = r'.*?\.(?:midi|html|pdf|png|jpe?g|txt|i?ly|signature|css|zip)$|VERSION', + find_files = r'.*?\.(?:midi|html|pdf|png|jpe?g|txt|i?ly|signature|css|zip|js|..\.idx|php)$|VERSION', exclude_files = r'lily-[0-9a-f]+.*\.(pdf|txt)') # actual mirrorring stuff -- 2.39.2