diff --git a/collects/scribble/html-render.ss b/collects/scribble/html-render.ss index 4e1ebf84b0..e96e87c625 100644 --- a/collects/scribble/html-render.ss +++ b/collects/scribble/html-render.ss @@ -721,11 +721,15 @@ `((span ([title ,(hover-element-text e)]) ,@(render-plain-element e part ri)))] [(script-element? e) - `((script ([type ,(script-element-type e)]) - ,(apply literal `("\n" ,@(script-element-script e) "\n"))) - ;; mynoscript hack doesn't always work (see hack in scribble-common.js) - (noscript ,@(render-plain-element e part ri)) - )] + (let* ([t `[type ,(script-element-type e)]] + [s (script-element-script e)] + [s (if (list? s) + `(script (,t) ,(apply literal `("\n" ,@s "\n"))) + `(script (,t [src ,s])))]) + (list s + ;; mynoscript hack doesn't always work (see the + ;; (commented) hack in scribble-common.js) + `(noscript ,@(render-plain-element e part ri))))] [(target-element? e) `((a ([name ,(format "~a" (anchor-name (tag-key (target-element-tag e) ri)))])) diff --git a/collects/scribble/struct.ss b/collects/scribble/struct.ss index 2ef0942f9f..dcad36256e 100644 --- a/collects/scribble/struct.ss +++ b/collects/scribble/struct.ss @@ -166,7 +166,7 @@ [(aux-element element) ()] [(hover-element element) ([text string?])] [(script-element element) ([type string?] - [script (listof string?)])] + [script (or/c path-string? (listof string?))])] ;; specific renders support other elements, especially strings [with-attributes ([style any/c] @@ -176,7 +176,7 @@ [parent (or/c false/c part?)] [info any/c])] - [target-url ([addr (or/c string? path?)] [style any/c])] + [target-url ([addr path-string?] [style any/c])] [url-anchor ([name string?])] [image-file ([path (or/c path-string? (cons/c (one-of/c 'collects) diff --git a/collects/scribblings/main/private/make-search.ss b/collects/scribblings/main/private/make-search.ss index 211953f4ce..4e02559ab1 100644 --- a/collects/scribblings/main/private/make-search.ss +++ b/collects/scribblings/main/private/make-search.ss @@ -13,11 +13,16 @@ (only-in scheme/class send) (only-in xml xexpr->string) (only-in setup/dirs find-doc-dir) - "utils.ss") + "utils.ss" + scheme/runtime-path + (for-syntax (only-in scheme/base #%datum))) (provide make-search) +(define-runtime-path search-script "search.js") + (define (make-script user-dir? renderer sec ri) + (define dest-dir (send renderer get-dest-directory)) (define span-classes null) ;; To make the index smaller, html contents is represented as one of these: ;; - a string @@ -118,547 +123,48 @@ ;; Note: using ~s to have javascript-quoted strings (format "[~s,~s,~a,~a]" text href html from-libs))) - @script[#:noscript @list{Sorry, you must have JavaScript to use this page.}]{ - // the url of the main doc tree, for compact url - // representation (see also the UncompactUrl function) - plt_main_url = @(format "~s" main-url); - // classes to be used for compact representation of html strings in - // plt_search_data below (see also the UncompactHtml function) - plt_span_classes = [ - @(add-between (map (lambda (x) (format "~s" (car x))) - (reverse span-classes)) - ",\n ")]; - // this array has an entry for each index link: [text, url, html, from_lib] - // - text is a string holding the indexed text - // - url holds the link (">" prefix means relative to plt_main_url) - // - html holds either a string, or [idx, html] where idx is an - // index into plt_span_classes (note: this is recursive) - // - from_lib is an array of module names for bound identifiers, - // or the string "module" for a module entry - plt_search_data = [ - @(add-between l ",\n")]; - // array of pointers to the previous array, for items that are manuals - plt_manual_ptrs = { - @(let* ([ms (hash-map manual-refs cons)] - [ms (sort ms < #:key cdr)] - [ms (map (lambda (x) (format "~s: ~a" (car x) (cdr x))) ms)]) - (add-between ms ",\n "))}; + (with-output-to-file (build-path dest-dir "plt-index.js") #:exists 'truncate + (lambda () + (for-each + display + @`{// the url of the main doc tree, for compact url + // representation (see also the UncompactUrl function) + plt_main_url = @,(format "~s" main-url);@"\n" + // classes to be used for compact representation of html strings in + // plt_search_data below (see also the UncompactHtml function) + plt_span_classes = [ + @,@(add-between (map (lambda (x) (format "~s" (car x))) + (reverse span-classes)) + ",\n ")];@"\n" + // this array has an entry of four items for each index link: + // - text is a string holding the indexed text + // - url holds the link (">" prefix means relative to plt_main_url) + // - html holds either a string, or [idx, html] where idx is an + // index into plt_span_classes (note: this is recursive) + // - from_lib is an array of module names for bound identifiers, + // or the string "module" for a module entry + plt_search_data = [ + @,@(add-between l ",\n")];@"\n" + // array of pointers to the previous array, for items that are manuals + plt_manual_ptrs = { + @,@(let* ([ms (hash-map manual-refs cons)] + [ms (sort ms < #:key cdr)] + [ms (map (lambda (x) (format "~s: ~a" (car x) (cdr x))) + ms)]) + (add-between ms ",\n "))}; + }))) - // Globally visible bindings - var key_handler, toggle_panel, hide_prefs, new_query, refine_query, - set_show_manuals, set_show_manual_titles, set_results_num, - set_type_delay, set_highlight_color; + (let ([js (build-path dest-dir "search.js")]) + (when (file-exists? js) (delete-file js)) + (copy-file search-script js)) - (function(){ - - // Configuration options (use || in case a cookie exists but is empty) - var manual_settings = parseInt(GetCookie("PLT_ManualSettings",1)); - var show_manuals = manual_settings % 10; - var show_manual_titles = ((manual_settings - show_manuals) / 10) > 0; - var results_num = (parseInt(GetCookie("PLT_ResultsNum", false)) || 20); - var type_delay = (parseInt(GetCookie("PLT_TypeDelay", false)) || 300); - var highlight_color = (GetCookie("PLT_HighlightColor", false) || "#ffd"); - var background_color = "#f8f8f8"; - - var query, status, results_container, result_links, - prev_page_link, next_page_link; - - // tabIndex fields are set: - // 1 query - // 2 index links - // 3 help/pref toggle - // 4 pref widgets - // -1 prev/next page (un-tab-able) - - function InitializeSearch() { - var n; - n = document.getElementById("plt_search_container").parentNode; - // hack the table in - n.innerHTML = '' - +'' - +'' - +'' - +'' - +'' - +'' - +'
' - +'' - +'' - +'[?]' - +'[!]' - +'
' - +'<<' - +'' - +' ' - +'' - +'>>' - +'
' - +'' - +'
'; - // get the widgets we use - query = document.getElementById("search_box"); - status = document.getElementById("search_status"); - prev_page_link = document.getElementById("prev_page_link"); - next_page_link = document.getElementById("next_page_link"); - // result_links is the array of result link pairs - result_links = new Array(); - n = document.getElementById("search_result"); - results_container = n.parentNode; - results_container.normalize(); - result_links.push(n); - AdjustResultsNum(); - // get search string - if (location.search.length > 0) { - var paramstrs = location.search.substring(1).split(/[@";"&]/); - for (var i=0@";" i results_num) - results_container.removeChild(result_links.pop()); - while (result_links.length < results_num) { - var n = result_links[0].cloneNode(true); - result_links.push(n); - results_container.appendChild(n); - } - } - - // constants for Compare(() results; - // `rexact' is for an actual exact match, so we know that we matched - // *something* and can show exact matches as such, in other words: - // - < exact => this match is inexact - // - = exact => does not affect the exactness of this match - // - > exact => this is an exact match as far as this predicate goes - var C_fail = 0, C_match = 1, C_prefix = 2, C_exact = 3, C_rexact = 4; - - function Compare(pat, str) { - var i = str.indexOf(pat); - if (i < 0) return C_fail; - else if (i > 0) return C_match; - else if (pat.length == str.length) return C_rexact; - else return C_prefix; - } - function MaxCompares(pat, strs) { - var r = C_fail; - for (var i=0@";" i)/, ""); - } - - function CompileTerm(term) { - var flag = ((term.search(/^[LMT]:/)==0) && term.substring(0,1)); - if (flag) term = term.substring(2); - term = term.toLowerCase(); - switch(flag) { - case "L": return function(x) { - if (!x[3]) return C_fail; - if (x[3] == "module") return Compare(term,x[0]); // rexact allowed! - return (MaxCompares(term,x[3]) >= C_exact) ? C_exact : C_fail; - } - case "M": return function(x) { - if (!x[3]) return C_fail; - if (x[3] == "module") return Compare(term,x[0]); // rexact allowed! - return (MaxCompares(term,x[3]) >= C_match) ? C_exact : C_fail; - } - case "T": return function(x) { - if (Compare(term,UrlToManual(x[1])) < C_exact) return C_fail; - else if (x[1].search(/\/index\.html$/) > 0) return C_rexact; - else return C_exact; - } - default: return function(x) { - switch (Compare(term,x[0])) { - case C_fail: return C_fail; - case C_match: case C_prefix: return C_match; - case C_exact: case C_rexact: return (x[3] ? C_rexact : C_match); - } - } - } - } - - var last_search_term, last_search_term_raw; - var search_results = [], first_search_result, exact_results_num; - function DoSearch() { - var term = query.value; - if (term == last_search_term_raw) return; - last_search_term_raw = term; - term = term.replace(/\s\s*/g," ") // single spaces - .replace(/^\s/g,"").replace(/\s$/g,""); // trim edge spaces - if (term == last_search_term) return; - last_search_term = term; - status.innerHTML = "Searching " + plt_search_data.length + " entries"; - var terms = (term=="") ? [] : term.split(/ /); - for (var i=0@";" i= C_rexact) exact_results.push(plt_search_data[i]); - else if (r > C_fail) search_results.push(plt_search_data[i]); - } - exact_results_num = exact_results.length; - if (exact_results.length > 0) - search_results = exact_results.concat(search_results); - } - first_search_result = 0; - status.innerHTML = "" + search_results.length + " entries found"; - query.style.backgroundColor = - ((search_results.length == 0) && (term != "")) ? "#ffe0e0" : "white"; - UpdateResults(); - } - - function UncompactUrl(url) { - return url.replace(/^>/, plt_main_url); - } - - function UncompactHtml(x) { - if (typeof x == "string") { - return x; - } else if (! (x instanceof Array)) { - alert("Internal error in PLT docs"); - } else if ((x.length == 2) && (typeof(x[0]) == "number")) { - return '' + UncompactHtml(x[1]) + ''; - } else { - var s = ""; - for (var i=0@";" i= search_results.length) - first_search_result = 0; - for (var i=0@";" i 0)) { - note = 'provided from '; - for (var j=0@";" j' - + desc[j] + ''; - } else if (desc == "module") { - note = 'module'; - } - if (show_manuals == 2 || (show_manuals == 1 && !desc)) { - var manual = UrlToManual(res[1]), - idx = (show_manual_titles && plt_manual_ptrs[manual]); - note = (note ? (note + " ") : ""); - note += 'in ' - + '' - + ((typeof idx == "number") - ? (''+UncompactHtml(plt_search_data[idx][2])+'') - : manual) - + ''; - } - if (note) - note = '  ' + note + ''; - result_links[i].innerHTML = - '' - + UncompactHtml(res[2]) + '' + (note || ""); - result_links[i].style.backgroundColor = - (n < exact_results_num) ? highlight_color : background_color; - result_links[i].style.display = "block"; - } else { - result_links[i].style.display = "none"; - } - } - var exact = Math.min((exact_results_num - first_search_result), - results_num); - exact = (exact <= 0) ? '' - : ' (' - + ((exact == results_num) ? 'all' : exact) - + ' exact)'; - if (search_results.length == 0) - status.innerHTML = ((last_search_term=="") ? "" : "No matches found"); - else if (search_results.length <= results_num) - status.innerHTML = "Showing all matches" + exact; - else - status.innerHTML = - "Showing " - + (first_search_result+1) + "-" - + Math.min(first_search_result+results_num,search_results.length) - + exact - + " of " + search_results.length - + ((search_results.length==plt_search_data.length) ? "" : " matches"); - prev_page_link.style.color = - (first_search_result-results_num >= 0) ? "black" : "#e0e0e0"; - next_page_link.style.color = - (first_search_result+results_num < search_results.length) - ? "black" : "#e0e0e0"; - } - - var search_timer = null; - function HandleKeyEvent(event) { - if (search_timer != null) { - var t = search_timer; - search_timer = null; - clearTimeout(t); - } - var key = null; - if (typeof event == "string") key = event; - else if (event) { - switch (event.which || event.keyCode) { - case 13: key = "Enter"; break; - case 33: key = "PgUp"; break; - case 34: key = "PgDn"; break; - } - } - switch (key) { - case "Enter": // enter with no change scrolls - if (query.value == last_search_term_raw) { - first_search_result += results_num; - UpdateResults(); - } else { - DoSearch(); - } - return false; - case "PgUp": - DoSearch(); // in case we didn't update it yet - first_search_result -= results_num; - UpdateResults(); - return false; - case "PgDn": - DoSearch(); // in case we didn't update it yet - if (first_search_result + results_num < search_results.length) { - first_search_result += results_num; - UpdateResults(); - } - return false; - } - search_timer = setTimeout(DoSearch, type_delay); - return true; - } - key_handler = HandleKeyEvent; - - // use this one to set the query field without jumping to the current - // url again, since some browsers will reload the whole page for that - // (it would be nice if there was a way to add it to the history too) - function NewQuery(node) { - var m = node.href.search(/[?]q=[^?&@";"]+$/); - if (m < 0) return true; - else { - query.value = decodeURIComponent(node.href.substring(m+3)); - query.focus(); - DoSearch(); - return false; - } - } - new_query = NewQuery; - - // and this appends the the query to the current value (it's hooked - // on the oncontextmenu handler that doesn't work everywhere, but at - // least in FF and IE) - function RefineQuery(node) { - var m = node.href.search(/[?]q=[^?&@";"]+$/); - if (m < 0) return true; - m = decodeURIComponent(node.href.substring(m+3)); - if (query.value.indexOf(m) >= 0) return true; - else { - query.value = m + " " + query.value; - query.focus(); - DoSearch(); - return false; - } - } - refine_query = RefineQuery; - - var panels_shown = { "help": false, "prefs": false }; - function TogglePanel(name) { - var shown = !panels_shown[name]; - panels_shown[name] = shown; - if (shown && (name == "prefs")) { - document.getElementById("show_manuals_pref").selectedIndex - = show_manuals; - document.getElementById("show_manual_titles_pref").checked - = show_manual_titles; - document.getElementById("results_num_pref").value = results_num; - document.getElementById("type_delay_pref").value = type_delay; - document.getElementById("highlight_color_pref").value = highlight_color; - } - document.getElementById(name + "_panel").style.display = - shown ? "table-cell" : "none"; - } - toggle_panel = TogglePanel; - - function HidePrefs(event) { - if ((event.which || event.keyCode) == 27) { - query.focus(); - panels_shown["prefs"] = true; - TogglePanel("prefs"); - } - } - hide_prefs = HidePrefs; - - function SetShowManuals(inp) { - if (inp.selectedIndex != show_manuals) { - show_manuals = inp.selectedIndex; - SetCookie("PLT_ManualSettings", show_manuals+(show_manual_titles?10:0)); - UpdateResults(); - } - } - set_show_manuals = SetShowManuals; - - function SetShowManualTitles(inp) { - if (inp.checked != show_manual_titles) { - show_manual_titles = inp.checked; - SetCookie("PLT_ManualSettings", show_manuals+(show_manual_titles?10:0)); - UpdateResults(); - } - } - set_show_manual_titles = SetShowManualTitles; - - function SetResultsNum(inp) { - var n = (parseInt(inp.value.replace(/[^0-9]+/g,"")) || results_num); - inp.value = n; - if (n != results_num) { - results_num = n; - SetCookie("PLT_ResultsNum", results_num); - AdjustResultsNum(); - UpdateResults(); - } - } - set_results_num = SetResultsNum; - - function SetTypeDelay(inp) { - var n = (parseInt(inp.value.replace(/[^0-9]+/g,"")) || type_delay); - inp.value = n; - if (n != type_delay) { - type_delay = n; - SetCookie("PLT_TypeDelay", type_delay); - } - } - set_type_delay = SetTypeDelay; - - function SetHighlightColor(inp) { - var c = (inp.value.replace(/[^a-zA-Z0-9#]/g,"") || highlight_color); - inp.value = c; - if (c != highlight_color) { - highlight_color = c; - SetCookie("PLT_HighlightColor", highlight_color); - UpdateResults(); - } - } - set_highlight_color = SetHighlightColor; - - window.onload = InitializeSearch; - - })(); - }) + (list + (script-ref "plt-index.js" + #:noscript @list{Sorry, you must have JavaScript to use this page.}) + (script-ref "search.js") + (make-element (make-with-attributes #f '((id . "plt_search_container"))) + null))) (define (make-search user-dir?) - (make-splice - (list - (make-delayed-block - (lambda (r s i) (make-paragraph (list (make-script user-dir? r s i))))) - (make-element (make-with-attributes #f '((id . "plt_search_container"))) - null)))) + (make-delayed-block (lambda (r s i) + (make-paragraph (make-script user-dir? r s i))))) diff --git a/collects/scribblings/main/private/utils.ss b/collects/scribblings/main/private/utils.ss index 911a7be639..2eac1e2899 100644 --- a/collects/scribblings/main/private/utils.ss +++ b/collects/scribblings/main/private/utils.ss @@ -8,7 +8,7 @@ scheme/list setup/dirs) -(provide main-page script) +(provide main-page script script-ref) (define page-info (let ([links (filter pair? links)]) @@ -19,6 +19,9 @@ (define (script #:noscript [noscript null] . body) (make-script-element #f noscript "text/javascript" (flatten body))) +(define (script-ref #:noscript [noscript null] path) + (make-script-element #f noscript "text/javascript" path)) + ;; the second argument specifies installation/user specific, and if ;; it's missing, then it's a page with a single version (define (main-page id [installation-specific? '?]) diff --git a/collects/scribblings/scribble/struct.scrbl b/collects/scribblings/scribble/struct.scrbl index edcbdd2316..cd0031f7cd 100644 --- a/collects/scribblings/scribble/struct.scrbl +++ b/collects/scribblings/scribble/struct.scrbl @@ -485,11 +485,13 @@ over the element's content.} @defstruct[(script-element element) ([type string?] - [script (listof string?)])]{ + [script (or/c path-string? + (listof string?))])]{ For HTML rendering, when scripting is enabled in the browser, @scheme[script] is used for the element instead of its normal -content. The @scheme[type] string is normally +content---it can be either path naming a script file to refer to, or +the contents of the script. The @scheme[type] string is normally @scheme["text/javascript"].} @@ -560,7 +562,7 @@ Computed for each part by the @techlink{collect pass}. } -@defstruct[target-url ([addr string?] +@defstruct[target-url ([addr path-string?] [style any/c])]{ Used as a style for an @scheme[element]. The @scheme[style] at this