[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
branch master updated: * js/info.js: Exdent entire body of file (except
From: |
Gavin D. Smith |
Subject: |
branch master updated: * js/info.js: Exdent entire body of file (except inside a string constant) in attempt to make nested functions more apparent. |
Date: |
Sat, 05 Oct 2024 16:12:29 -0400 |
This is an automated email from the git hooks/post-receive script.
gavin pushed a commit to branch master
in repository texinfo.
The following commit(s) were added to refs/heads/master by this push:
new 80cfe6f2aa * js/info.js: Exdent entire body of file (except inside a
string constant) in attempt to make nested functions more apparent.
80cfe6f2aa is described below
commit 80cfe6f2aa34efcac07b014c93fa9029e2f61430
Author: Gavin Smith <gavinsmith0123@gmail.com>
AuthorDate: Sat Oct 5 21:11:45 2024 +0100
* js/info.js: Exdent entire body of file (except inside a string
constant) in attempt to make nested functions more apparent.
---
ChangeLog | 5 +
js/info.js | 4009 ++++++++++++++++++++++++++++++------------------------------
2 files changed, 2011 insertions(+), 2003 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index cb8a765e2f..8b40d430ae 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2024-10-05 Gavin Smith <gavinsmith0123@gmail.com>
+
+ * js/info.js: Exdent entire body of file (except inside a string
+ constant) in attempt to make nested functions more apparent.
+
2024-10-05 Patrice Dumas <pertusus@free.fr>
* tp/Texinfo/XS/convert/call_html_cxx_function.cpp,
diff --git a/js/info.js b/js/info.js
index 1091f1c394..63ae668e1f 100644
--- a/js/info.js
+++ b/js/info.js
@@ -16,484 +16,487 @@
You should have received a copy of the GNU General Public License
along with GNU Texinfo. If not, see <http://www.gnu.org/licenses/>. */
-(function (features, user_config) {
- "use strict";
-
- /*-------------------.
- | Define constants. |
- `-------------------*/
-
- /** To override those default parameters, define 'INFO_CONFIG' in the global
- environment before loading this script. */
- var config = {
- EXT: ".html",
- INDEX_NAME: "index.html",
- INDEX_ID: "index",
- CONTENTS_ID: "SEC_Contents",
- MAIN_ANCHORS: ["Top"],
- WARNING_TIMEOUT: 3000,
- SCREEN_MIN_WIDTH: 700,
- LOCAL_HTML_PAGE_PATTERN: "^([^:/]*[.](html|htm|xhtml))?([#].*)?$",
- SHOW_SIDEBAR_HTML: '<span class="hide-icon">⇛</span>',
- HIDE_SIDEBAR_HTML: '<span class="hide-icon">⇚</span><span
class="hide-text"></span>',
- SHOW_SIDEBAR_TOOLTIP: 'Show navigation sidebar',
- HIDE_SIDEBAR_TOOLTIP: 'Hide navigation sidebar',
-
- // hooks:
- /** Define a function called after 'DOMContentLoaded' event in
- the INDEX_NAME context.
- @type {function (): void}*/
- on_main_load: null,
- /** Define a function called after 'DOMContentLoaded' event in
- the iframe context.
- @type {(function (): void)} */
- on_iframe_load: null
- };
-
- /*-------------------.
- | State Management. |
- `-------------------*/
-
- /** A 'store' is an object managing the state of the application and having
- a dispatch method which accepts actions as parameter. This method is
- the only way to update the state.
- @typedef {function (Action): void} Action_consumer
- @type {{dispatch: Action_consumer, state?: any, listeners?: any[]}}. */
- var store;
-
- var section_names = [
- 'top', 'chapter', 'unnumbered', 'chapheading', 'appendix',
- 'section', 'unnumberedsec', 'heading', 'appendixsec',
- 'subsection', 'unnumberedsubsec', 'subheading', 'appendixsubsec',
- 'subsubsection', 'unnumberedsubsubsec', 'subsubheading',
- 'appendixsubsubsec' ];
-
- /** Create a Store that calls its listeners at each state change.
- @arg {function (Object, Action): Object} reducer
- @arg {Object} state */
- function
- Store (reducer, state)
- {
- this.listeners = [];
- this.reducer = reducer;
- this.state = state;
- }
+# Note that the entire body of this file apart from the first and last
+# lines are within a function call.
- /** @arg {Action} action */
- Store.prototype.dispatch = function dispatch (action) {
- var new_state = this.reducer (this.state, action);
- if (new_state !== this.state)
+(function (features, user_config) {
+"use strict";
+
+/*-------------------.
+| Define constants. |
+`-------------------*/
+
+/** To override those default parameters, define 'INFO_CONFIG' in the global
+ environment before loading this script. */
+var config = {
+ EXT: ".html",
+ INDEX_NAME: "index.html",
+ INDEX_ID: "index",
+ CONTENTS_ID: "SEC_Contents",
+ MAIN_ANCHORS: ["Top"],
+ WARNING_TIMEOUT: 3000,
+ SCREEN_MIN_WIDTH: 700,
+ LOCAL_HTML_PAGE_PATTERN: "^([^:/]*[.](html|htm|xhtml))?([#].*)?$",
+ SHOW_SIDEBAR_HTML: '<span class="hide-icon">⇛</span>',
+ HIDE_SIDEBAR_HTML: '<span class="hide-icon">⇚</span><span
class="hide-text"></span>',
+ SHOW_SIDEBAR_TOOLTIP: 'Show navigation sidebar',
+ HIDE_SIDEBAR_TOOLTIP: 'Hide navigation sidebar',
+
+ // hooks:
+ /** Define a function called after 'DOMContentLoaded' event in
+ the INDEX_NAME context.
+ @type {function (): void}*/
+ on_main_load: null,
+ /** Define a function called after 'DOMContentLoaded' event in
+ the iframe context.
+ @type {(function (): void)} */
+ on_iframe_load: null
+};
+
+/*-------------------.
+| State Management. |
+`-------------------*/
+
+/** A 'store' is an object managing the state of the application and having
+ a dispatch method which accepts actions as parameter. This method is
+ the only way to update the state.
+ @typedef {function (Action): void} Action_consumer
+ @type {{dispatch: Action_consumer, state?: any, listeners?: any[]}}. */
+var store;
+
+var section_names = [
+ 'top', 'chapter', 'unnumbered', 'chapheading', 'appendix',
+ 'section', 'unnumberedsec', 'heading', 'appendixsec',
+ 'subsection', 'unnumberedsubsec', 'subheading', 'appendixsubsec',
+ 'subsubsection', 'unnumberedsubsubsec', 'subsubheading',
+ 'appendixsubsubsec' ];
+
+/** Create a Store that calls its listeners at each state change.
+ @arg {function (Object, Action): Object} reducer
+ @arg {Object} state */
+function
+Store (reducer, state)
+{
+ this.listeners = [];
+ this.reducer = reducer;
+ this.state = state;
+}
+
+/** @arg {Action} action */
+Store.prototype.dispatch = function dispatch (action) {
+ var new_state = this.reducer (this.state, action);
+ if (new_state !== this.state)
+ {
+ this.state = new_state;
+ if (window["INFO_DEBUG"])
+ console.log ("state: ", new_state);
+ this.listeners.forEach (function (listener) {
+ listener (new_state);
+ });
+ }
+};
+
+/** Build a store delegate that will forward its actions to a "real"
+ store that can be in a different browsing context. */
+function
+Remote_store ()
+{
+ /* The browsing context containing the real store. */
+ this.delegate = window.parent;
+}
+
+/** Dispatch ACTION to the delegate browing context. This method must be
+ used in conjunction of an event listener on "message" events in the
+ delegate browsing context which must forwards ACTION to an actual store.
+ @arg {Action} action */
+Remote_store.prototype.dispatch = function dispatch (action) {
+ this.delegate.postMessage ({ message_kind: "action", action: action }, "*");
+};
+
+/** @typedef {{type: string, [x: string]: any}} Action - Payloads
+ of information meant to be treated by the store which can receive them
+ using the 'store.dispatch' method. */
+
+/* Place holder for action creators. */
+var actions = {
+ /** Return an action that makes LINKID the current page.
+ @arg {string} linkid - link identifier
+ @arg {string|false} [history] - method name that will be applied on
+ the 'window.history' object. */
+ set_current_url: function (linkid, history, clicked) {
+ if (undef_or_null (history))
+ history = "pushState";
+ return { type: "current-url", url: linkid,
+ history: history, clicked: clicked };
+ },
+
+ /** Set current URL to the node corresponding to POINTER which is an
+ id refering to a linkid (such as "*TOP*" or "*END*"). */
+ set_current_url_pointer: function (pointer) {
+ return { type: "current-url", pointer: pointer, history: "pushState" };
+ },
+
+ /** @arg {string} dir */
+ navigate: function (dir) {
+ return { type: "navigate", direction: dir, history: "pushState" };
+ },
+
+ /** @arg {{[id: string]: string}} links */
+ cache_links: function (links) {
+ return { type: "cache-links", links: links };
+ },
+
+ /** @arg {NodeListOf<Element>} links */
+ cache_index_links: function (links) {
+ var dict = {};
+ var text0 = "", text1 = ""; // for subentries
+ for (var i = 0; i < links.length; i += 1)
{
- this.state = new_state;
- if (window["INFO_DEBUG"])
- console.log ("state: ", new_state);
- this.listeners.forEach (function (listener) {
- listener (new_state);
- });
+ var link = links[i];
+ var link_cl = link.classList;
+ var text = link.textContent;
+ if (link_cl.contains("index-entry-level-2"))
+ {
+ text = text0 + "; " + text1 + "; " + text;
+ }
+ else if (link_cl.contains("index-entry-level-1"))
+ {
+ text1 = text;
+ text = text0 + "; " + text;
+ }
+ else
+ {
+ text0 = text;
+ }
+
+ if ((link = link.nextSibling)
+ && link.classList.contains("printindex-index-section")
+ && (link = link.firstChild))
+ {
+ dict[text] = href_hash (link_href (link));
+ }
}
- };
+ return { type: "cache-index-links", links: dict };
+ },
+
+ /** Show or hide the help screen. */
+ show_help: function () {
+ return { type: "help", visible: true };
+ },
+
+ /** Make the text input INPUT visible. If INPUT is a falsy value then
+ hide current text input.
+ @arg {string} input */
+ show_text_input: function (input) {
+ return { type: "input", input: input };
+ },
+
+ /** Hide the current current text input. */
+ hide_text_input: function () {
+ return { type: "input", input: null };
+ },
+
+ /** @arg {string} msg */
+ warn: function (msg) {
+ return { type: "warning", msg: msg };
+ },
+
+ window_title: function (title) {
+ return { type: "window-title", title: title };
+ },
+
+ /** Search EXP in the whole manual.
+ @arg {RegExp|string} exp*/
+ search: function (exp) {
+ var rgxp;
+ if (typeof exp === "object")
+ rgxp = exp;
+ else if (exp === "")
+ rgxp = null;
+ else
+ rgxp = new RegExp (exp, "i");
- /** Build a store delegate that will forward its actions to a "real"
- store that can be in a different browsing context. */
- function
- Remote_store ()
- {
- /* The browsing context containing the real store. */
- this.delegate = window.parent;
+ return { type: "search-init", regexp: rgxp, input: exp };
}
-
- /** Dispatch ACTION to the delegate browing context. This method must be
- used in conjunction of an event listener on "message" events in the
- delegate browsing context which must forwards ACTION to an actual store.
- @arg {Action} action */
- Remote_store.prototype.dispatch = function dispatch (action) {
- this.delegate.postMessage ({ message_kind: "action", action: action },
"*");
- };
-
- /** @typedef {{type: string, [x: string]: any}} Action - Payloads
- of information meant to be treated by the store which can receive them
- using the 'store.dispatch' method. */
-
- /* Place holder for action creators. */
- var actions = {
- /** Return an action that makes LINKID the current page.
- @arg {string} linkid - link identifier
- @arg {string|false} [history] - method name that will be applied on
- the 'window.history' object. */
- set_current_url: function (linkid, history, clicked) {
- if (undef_or_null (history))
- history = "pushState";
- return { type: "current-url", url: linkid,
- history: history, clicked: clicked };
- },
-
- /** Set current URL to the node corresponding to POINTER which is an
- id refering to a linkid (such as "*TOP*" or "*END*"). */
- set_current_url_pointer: function (pointer) {
- return { type: "current-url", pointer: pointer, history: "pushState" };
- },
-
- /** @arg {string} dir */
- navigate: function (dir) {
- return { type: "navigate", direction: dir, history: "pushState" };
- },
-
- /** @arg {{[id: string]: string}} links */
- cache_links: function (links) {
- return { type: "cache-links", links: links };
- },
-
- /** @arg {NodeListOf<Element>} links */
- cache_index_links: function (links) {
- var dict = {};
- var text0 = "", text1 = ""; // for subentries
- for (var i = 0; i < links.length; i += 1)
- {
- var link = links[i];
- var link_cl = link.classList;
- var text = link.textContent;
- if (link_cl.contains("index-entry-level-2"))
- {
- text = text0 + "; " + text1 + "; " + text;
- }
- else if (link_cl.contains("index-entry-level-1"))
- {
- text1 = text;
- text = text0 + "; " + text;
- }
- else
- {
- text0 = text;
- }
-
- if ((link = link.nextSibling)
- && link.classList.contains("printindex-index-section")
- && (link = link.firstChild))
- {
- dict[text] = href_hash (link_href (link));
- }
- }
- return { type: "cache-index-links", links: dict };
- },
-
- /** Show or hide the help screen. */
- show_help: function () {
- return { type: "help", visible: true };
- },
-
- /** Make the text input INPUT visible. If INPUT is a falsy value then
- hide current text input.
- @arg {string} input */
- show_text_input: function (input) {
- return { type: "input", input: input };
- },
-
- /** Hide the current current text input. */
- hide_text_input: function () {
- return { type: "input", input: null };
- },
-
- /** @arg {string} msg */
- warn: function (msg) {
- return { type: "warning", msg: msg };
- },
-
- window_title: function (title) {
- return { type: "window-title", title: title };
- },
-
- /** Search EXP in the whole manual.
- @arg {RegExp|string} exp*/
- search: function (exp) {
- var rgxp;
- if (typeof exp === "object")
- rgxp = exp;
- else if (exp === "")
- rgxp = null;
- else
- rgxp = new RegExp (exp, "i");
-
- return { type: "search-init", regexp: rgxp, input: exp };
- }
- };
-
- /** Update STATE based on the type of ACTION. This update is purely
- fonctional since STATE is not modified in place and a new state object
- is returned instead.
- @arg {Object} state
- @arg {Action} action
- @return {Object} a new state */
- /* eslint-disable complexity */
- /* A "big" switch has been preferred over splitting each case in a separate
- function combined with a dictionary lookup. */
- function
- updater (state, action)
- {
- var res = Object.assign ({}, state, { action: action });
- var linkid;
- switch (action.type)
+};
+
+/** Update STATE based on the type of ACTION. This update is purely
+ fonctional since STATE is not modified in place and a new state object
+ is returned instead.
+ @arg {Object} state
+ @arg {Action} action
+ @return {Object} a new state */
+/* eslint-disable complexity */
+/* A "big" switch has been preferred over splitting each case in a separate
+ function combined with a dictionary lookup. */
+function
+updater (state, action)
+{
+ var res = Object.assign ({}, state, { action: action });
+ var linkid;
+ switch (action.type)
+ {
+ case "cache-links":
{
- case "cache-links":
- {
- var nodes = Object.assign ({}, state.loaded_nodes);
- Object
- .keys (action.links)
- .forEach (function (key) {
- if (typeof action.links[key] === "object")
- nodes[key] = Object.assign ({}, nodes[key], action.links[key]);
- else
- nodes[key] = action.links[key];
- });
-
- return Object.assign (res, { loaded_nodes: nodes });
- }
- case "cache-index-links":
- {
- // Initially res.index is undefined, which is ignored.
- res.index = Object.assign ({}, res.index, action.links);
- return res;
- }
- case "section":
- {
- res.section_hash = action.section_hash;
- return res;
- }
- case "current-url":
- {
- if (document.body.getAttribute("show-sidebar") == "yes"
- && is_narrow_window ()
- && action.clicked === "in-page")
- return state;
- linkid = (action.pointer) ?
- state.loaded_nodes[action.pointer] : action.url;
-
- res.current = linkid;
- res.section_hash = null;
- res.history = action.history;
- res.text_input = null;
- res.warning = null;
- res.window_title = null;
- res.help = false;
- res.focus = false;
- res.highlight = null;
- res.loaded_nodes = Object.assign ({}, res.loaded_nodes);
- res.loaded_nodes[linkid] = res.loaded_nodes[linkid] || {};
- return res;
- }
- case "unfocus":
- {
- if (!res.focus)
- return state;
- else
- {
- res.help = false;
- res.text_input = null;
- res.focus = false;
- return res;
- }
- }
- case "help":
- {
- res.help = action.visible;
- res.focus = true;
- return res;
- }
- case "show-sidebar": // "yes" (show), "no" (hide) or "hide-if-narrow"
- {
- res.show_sidebar = action.show === "hide-if-narrow" &&
res.show_sidebar === "no" ? "no" : action.show;
- return res;
- }
- case "navigate":
- {
- var current = state.current;
- var link = linkid_split (state.current);
+ var nodes = Object.assign ({}, state.loaded_nodes);
+ Object
+ .keys (action.links)
+ .forEach (function (key) {
+ if (typeof action.links[key] === "object")
+ nodes[key] = Object.assign ({}, nodes[key], action.links[key]);
+ else
+ nodes[key] = action.links[key];
+ });
- /* Handle inner 'config.INDEX_NAME' anchors specially. */
- if (link.pageid === config.INDEX_ID)
- current = config.INDEX_ID;
+ return Object.assign (res, { loaded_nodes: nodes });
+ }
+ case "cache-index-links":
+ {
+ // Initially res.index is undefined, which is ignored.
+ res.index = Object.assign ({}, res.index, action.links);
+ return res;
+ }
+ case "section":
+ {
+ res.section_hash = action.section_hash;
+ return res;
+ }
+ case "current-url":
+ {
+ if (document.body.getAttribute("show-sidebar") == "yes"
+ && is_narrow_window ()
+ && action.clicked === "in-page")
+ return state;
+ linkid = (action.pointer) ?
+ state.loaded_nodes[action.pointer] : action.url;
+
+ res.current = linkid;
+ res.section_hash = null;
+ res.history = action.history;
+ res.text_input = null;
+ res.warning = null;
+ res.window_title = null;
+ res.help = false;
+ res.focus = false;
+ res.highlight = null;
+ res.loaded_nodes = Object.assign ({}, res.loaded_nodes);
+ res.loaded_nodes[linkid] = res.loaded_nodes[linkid] || {};
+ return res;
+ }
+ case "unfocus":
+ {
+ if (!res.focus)
+ return state;
+ else
+ {
+ res.help = false;
+ res.text_input = null;
+ res.focus = false;
+ return res;
+ }
+ }
+ case "help":
+ {
+ res.help = action.visible;
+ res.focus = true;
+ return res;
+ }
+ case "show-sidebar": // "yes" (show), "no" (hide) or "hide-if-narrow"
+ {
+ res.show_sidebar = action.show === "hide-if-narrow" &&
res.show_sidebar === "no" ? "no" : action.show;
+ return res;
+ }
+ case "navigate":
+ {
+ var current = state.current;
+ var link = linkid_split (state.current);
- var ids = state.loaded_nodes[current];
- linkid = ids[action.direction];
- if (!linkid)
- {
- // Look for sibling link in ToC.
- // Needed for (say) @subsection without corresponding @node.
- let toc_current =
- document.querySelector ('#slider a[toc-current="yes"]');
- if (toc_current)
- {
- let item_current = toc_current.parentNode; // 'li' element
- let nlink = (action.direction === "next"
- ? item_current.nextElementSibling
- : action.direction === "prev"
- ? item_current.previousElementSibling
- : action.direction === "up"
- ? item_current.parentNode.parentNode
- : null);
- if (nlink)
- nlink = nlink.firstElementChild;
- if (nlink && nlink.matches("a"))
- {
- let href = link_href (nlink)
- if (href)
- linkid = href_hash (href) ;
- }
- }
- }
- if (!linkid)
- {
- /* When CURRENT is in index but doesn't have the requested
- direction, ask its corresponding 'pageid'. */
- var is_index_ref =
- Object.keys (state.index)
- .reduce (function (acc, val) {
- return acc || state.index[val] === current;
- }, false);
- if (is_index_ref)
- {
- ids = state.loaded_nodes[link.pageid];
- linkid = ids[action.direction];
- }
- }
+ /* Handle inner 'config.INDEX_NAME' anchors specially. */
+ if (link.pageid === config.INDEX_ID)
+ current = config.INDEX_ID;
- if (!linkid)
- return state;
- else
- {
- res.current = linkid;
- res.section_hash = null;
- res.history = action.history;
- res.text_input = null;
- res.warning = null;
- res.window_title = null;
- res.help = false;
- res.highlight = null;
- res.focus = false;
- res.loaded_nodes = Object.assign ({}, res.loaded_nodes);
- res.loaded_nodes[action.url] = res.loaded_nodes[action.url] ||
{};
- return res;
- }
- }
- case "search-init":
- {
- res.search = {
- regexp: action.regexp || state.search.regexp,
- input: action.input || state.search.input,
- status: "ready",
- current_pageid: linkid_split (state.current).pageid,
- found: false
- };
- res.focus = false;
- res.help = false;
- res.text_input = null;
- res.warning = null;
- return res;
- }
- case "search-query":
- {
- res.search = Object.assign ({}, state.search);
- res.search.status = "searching";
- return res;
- }
- case "search-result":
- {
- res.search = Object.assign ({}, state.search);
- if (action.found)
- {
- res.search.status = "done";
- res.search.found = true;
- res.current = res.search.current_pageid;
- res.section_hash = null;
- res.history = "pushState";
- res.highlight = res.search.regexp;
- }
- else
- {
- var fwd = forward_pageid (state, state.search.current_pageid);
- if (fwd === null)
- {
- res.search.status = "done";
- res.warning = "Search failed: \"" + res.search.input + "\"";
- res.highlight = null;
- }
- else
- {
- res.search.current_pageid = fwd;
- res.search.status = "ready";
- }
- }
- return res;
- }
- case "input":
- {
- var needs_update = (state.text_input && !action.input)
- || (!state.text_input && action.input)
- || (state.text_input && action.input
- && state.text_input !== action.input);
+ var ids = state.loaded_nodes[current];
+ linkid = ids[action.direction];
+ if (!linkid)
+ {
+ // Look for sibling link in ToC.
+ // Needed for (say) @subsection without corresponding @node.
+ let toc_current =
+ document.querySelector ('#slider a[toc-current="yes"]');
+ if (toc_current)
+ {
+ let item_current = toc_current.parentNode; // 'li' element
+ let nlink = (action.direction === "next"
+ ? item_current.nextElementSibling
+ : action.direction === "prev"
+ ? item_current.previousElementSibling
+ : action.direction === "up"
+ ? item_current.parentNode.parentNode
+ : null);
+ if (nlink)
+ nlink = nlink.firstElementChild;
+ if (nlink && nlink.matches("a"))
+ {
+ let href = link_href (nlink)
+ if (href)
+ linkid = href_hash (href) ;
+ }
+ }
+ }
+ if (!linkid)
+ {
+ /* When CURRENT is in index but doesn't have the requested
+ direction, ask its corresponding 'pageid'. */
+ var is_index_ref =
+ Object.keys (state.index)
+ .reduce (function (acc, val) {
+ return acc || state.index[val] === current;
+ }, false);
+ if (is_index_ref)
+ {
+ ids = state.loaded_nodes[link.pageid];
+ linkid = ids[action.direction];
+ }
+ }
- if (!needs_update)
- return state;
- else
- {
- res.focus = Boolean (action.input);
- res.help = false;
- res.text_input = action.input;
- res.warning = null;
- return res;
- }
- }
- case "iframe-ready":
- {
- res.ready = Object.assign ({}, res.ready);
- res.ready[action.id] = true;
- return res;
- }
- case "echo":
- {
- res.echo = action.msg;
- return res;
- }
- case "window-title":
- {
- res.window_title = action.title;
- return res;
- }
- case "warning":
- {
- res.warning = action.msg;
- if (action.msg !== null)
+ if (!linkid)
+ return state;
+ else
+ {
+ res.current = linkid;
+ res.section_hash = null;
+ res.history = action.history;
res.text_input = null;
- return res;
- }
- default:
- console.error ("no reducer for action type:", action.type);
- return state;
+ res.warning = null;
+ res.window_title = null;
+ res.help = false;
+ res.highlight = null;
+ res.focus = false;
+ res.loaded_nodes = Object.assign ({}, res.loaded_nodes);
+ res.loaded_nodes[action.url] = res.loaded_nodes[action.url] || {};
+ return res;
+ }
}
- }
- /* eslint-enable complexity */
+ case "search-init":
+ {
+ res.search = {
+ regexp: action.regexp || state.search.regexp,
+ input: action.input || state.search.input,
+ status: "ready",
+ current_pageid: linkid_split (state.current).pageid,
+ found: false
+ };
+ res.focus = false;
+ res.help = false;
+ res.text_input = null;
+ res.warning = null;
+ return res;
+ }
+ case "search-query":
+ {
+ res.search = Object.assign ({}, state.search);
+ res.search.status = "searching";
+ return res;
+ }
+ case "search-result":
+ {
+ res.search = Object.assign ({}, state.search);
+ if (action.found)
+ {
+ res.search.status = "done";
+ res.search.found = true;
+ res.current = res.search.current_pageid;
+ res.section_hash = null;
+ res.history = "pushState";
+ res.highlight = res.search.regexp;
+ }
+ else
+ {
+ var fwd = forward_pageid (state, state.search.current_pageid);
+ if (fwd === null)
+ {
+ res.search.status = "done";
+ res.warning = "Search failed: \"" + res.search.input + "\"";
+ res.highlight = null;
+ }
+ else
+ {
+ res.search.current_pageid = fwd;
+ res.search.status = "ready";
+ }
+ }
+ return res;
+ }
+ case "input":
+ {
+ var needs_update = (state.text_input && !action.input)
+ || (!state.text_input && action.input)
+ || (state.text_input && action.input
+ && state.text_input !== action.input);
- /*-----------------------.
- | Context initializers. |
- `-----------------------*/
+ if (!needs_update)
+ return state;
+ else
+ {
+ res.focus = Boolean (action.input);
+ res.help = false;
+ res.text_input = action.input;
+ res.warning = null;
+ return res;
+ }
+ }
+ case "iframe-ready":
+ {
+ res.ready = Object.assign ({}, res.ready);
+ res.ready[action.id] = true;
+ return res;
+ }
+ case "echo":
+ {
+ res.echo = action.msg;
+ return res;
+ }
+ case "window-title":
+ {
+ res.window_title = action.title;
+ return res;
+ }
+ case "warning":
+ {
+ res.warning = action.msg;
+ if (action.msg !== null)
+ res.text_input = null;
+ return res;
+ }
+ default:
+ console.error ("no reducer for action type:", action.type);
+ return state;
+ }
+}
+/* eslint-enable complexity */
+
+/*-----------------------.
+| Context initializers. |
+`-----------------------*/
+
+/** Initialize the index page of the manual which manages the state of the
+ application. */
+function
+init_index_page ()
+{
+ /*--------------------------.
+ | Component for help page. |
+ `--------------------------*/
- /** Initialize the index page of the manual which manages the state of the
- application. */
function
- init_index_page ()
+ Help_page ()
{
- /*--------------------------.
- | Component for help page. |
- `--------------------------*/
-
- function
- Help_page ()
- {
- this.id = "help-screen";
- /* Create help div element.*/
- var div = document.createElement ("div");
- div.setAttribute ("hidden", "true");
- div.classList.add ("modal");
- div.innerHTML = "\
+ this.id = "help-screen";
+ /* Create help div element.*/
+ var div = document.createElement ("div");
+ div.setAttribute ("hidden", "true");
+ div.classList.add ("modal");
+ div.innerHTML = "\
<div class=\"modal-content\">\
<span class=\"close\">×</span>\
<h2>Keyboard Shortcuts</h2>\
@@ -528,1728 +531,1728 @@
</table>\
</div>\
";
- var span = div.querySelector (".close");
- div.addEventListener ("click", function (event) {
- if (event.target === span || event.target === div)
- store.dispatch ({ type: "unfocus" });
- }, false);
-
- this.element = div;
- }
-
- Help_page.prototype.render = function render (state) {
- if (!state.help)
- this.element.style.display = "none";
- else
- {
- this.element.style.display = "block";
- this.element.focus ();
- }
- };
+ var span = div.querySelector (".close");
+ div.addEventListener ("click", function (event) {
+ if (event.target === span || event.target === div)
+ store.dispatch ({ type: "unfocus" });
+ }, false);
- /*---------------------------------.
- | Components for menu navigation. |
- `---------------------------------*/
+ this.element = div;
+ }
- /** @arg {string} id */
- function
- Search_input (id)
- {
- this.id = id;
- this.render = null;
- this.prompt = document.createTextNode ("Text search" + ": ");
+ Help_page.prototype.render = function render (state) {
+ if (!state.help)
+ this.element.style.display = "none";
+ else
+ {
+ this.element.style.display = "block";
+ this.element.focus ();
+ }
+ };
- /* Create input div element.*/
- var div = document.createElement ("div");
- div.setAttribute ("hidden", "true");
- div.appendChild (this.prompt);
- this.element = div;
-
- /* Create input element.*/
- var input = document.createElement ("input");
- input.setAttribute ("type", "search");
- this.input = input;
- this.element.appendChild (input);
-
- /* Define a special key handler when 'this.input' is focused and
- visible.*/
- this.input.addEventListener ("keydown", (function (event) {
- if (is_escape_key (event.key))
- store.dispatch ({ type: "unfocus" });
- else if (event.key === "Enter")
- store.dispatch (actions.search (this.input.value));
- event.stopPropagation ();
- }).bind (this));
- }
+ /*---------------------------------.
+ | Components for menu navigation. |
+ `---------------------------------*/
- Search_input.prototype.show = function show () {
- /* Display previous search. */
- var search = store.state.search;
- var input = search && (search.status === "done") && search.input;
- if (input)
- this.prompt.textContent = "Text search (default " + input + "): ";
- this.element.removeAttribute ("hidden");
- this.input.focus ();
- };
+ /** @arg {string} id */
+ function
+ Search_input (id)
+ {
+ this.id = id;
+ this.render = null;
+ this.prompt = document.createTextNode ("Text search" + ": ");
- Search_input.prototype.hide = function hide () {
- this.element.setAttribute ("hidden", "true");
- this.input.value = "";
- };
+ /* Create input div element.*/
+ var div = document.createElement ("div");
+ div.setAttribute ("hidden", "true");
+ div.appendChild (this.prompt);
+ this.element = div;
+
+ /* Create input element.*/
+ var input = document.createElement ("input");
+ input.setAttribute ("type", "search");
+ this.input = input;
+ this.element.appendChild (input);
+
+ /* Define a special key handler when 'this.input' is focused and
+ visible.*/
+ this.input.addEventListener ("keydown", (function (event) {
+ if (is_escape_key (event.key))
+ store.dispatch ({ type: "unfocus" });
+ else if (event.key === "Enter")
+ store.dispatch (actions.search (this.input.value));
+ event.stopPropagation ();
+ }).bind (this));
+ }
- /** @arg {string} id */
- function
- Text_input (id)
- {
- this.id = id;
- this.data = null;
- this.datalist = null;
- this.render = null;
+ Search_input.prototype.show = function show () {
+ /* Display previous search. */
+ var search = store.state.search;
+ var input = search && (search.status === "done") && search.input;
+ if (input)
+ this.prompt.textContent = "Text search (default " + input + "): ";
+ this.element.removeAttribute ("hidden");
+ this.input.focus ();
+ };
- /* Create input div element.*/
- var div = document.createElement ("div");
- div.setAttribute ("hidden", "true");
- div.appendChild (document.createTextNode (id + ": "));
- this.element = div;
-
- /* Create input element.*/
- var input = document.createElement ("input");
- input.setAttribute ("type", "search");
- input.setAttribute ("list", id + "-data");
- this.input = input;
- this.element.appendChild (input);
-
- /* Define a special key handler when 'this.input' is focused and
- visible.*/
- this.input.addEventListener ("keydown", (function (event) {
- if (is_escape_key (event.key))
- store.dispatch ({ type: "unfocus" });
- else if (event.key === "Enter")
- {
- var linkid = this.data[this.input.value];
- if (linkid)
- {
- hide_sidebar_if_narrow ();
- store.dispatch (actions.set_current_url (linkid));
- }
- }
- event.stopPropagation ();
- }).bind (this));
- }
+ Search_input.prototype.hide = function hide () {
+ this.element.setAttribute ("hidden", "true");
+ this.input.value = "";
+ };
- /* Display a text input for searching through DATA.*/
- Text_input.prototype.show = function show (data) {
- if (!features || features.datalistelem)
- {
- var datalist = create_datalist (data);
- datalist.setAttribute ("id", this.id + "-data");
- this.data = data;
- this.datalist = datalist;
- this.element.appendChild (datalist);
- }
- this.element.removeAttribute ("hidden");
- this.input.focus ();
- };
+ /** @arg {string} id */
+ function
+ Text_input (id)
+ {
+ this.id = id;
+ this.data = null;
+ this.datalist = null;
+ this.render = null;
- Text_input.prototype.hide = function hide () {
- this.element.setAttribute ("hidden", "true");
- this.input.value = "";
- if (this.datalist)
+ /* Create input div element.*/
+ var div = document.createElement ("div");
+ div.setAttribute ("hidden", "true");
+ div.appendChild (document.createTextNode (id + ": "));
+ this.element = div;
+
+ /* Create input element.*/
+ var input = document.createElement ("input");
+ input.setAttribute ("type", "search");
+ input.setAttribute ("list", id + "-data");
+ this.input = input;
+ this.element.appendChild (input);
+
+ /* Define a special key handler when 'this.input' is focused and
+ visible.*/
+ this.input.addEventListener ("keydown", (function (event) {
+ if (is_escape_key (event.key))
+ store.dispatch ({ type: "unfocus" });
+ else if (event.key === "Enter")
{
- this.datalist.remove ();
- this.datalist = null;
+ var linkid = this.data[this.input.value];
+ if (linkid)
+ {
+ hide_sidebar_if_narrow ();
+ store.dispatch (actions.set_current_url (linkid));
+ }
}
- };
-
- function
- Minibuffer ()
- {
- /* Create global container.*/
- var elem = document.createElement ("div");
- elem.classList.add ("text-input");
+ event.stopPropagation ();
+ }).bind (this));
+ }
- var menu = new Text_input ("menu");
- menu.render = function (state) {
- if (state.text_input === "menu")
- {
- var current_menu = state.loaded_nodes[state.current].menu;
- if (current_menu)
- this.show (current_menu);
- else
- store.dispatch (actions.warn ("No menu in this node"));
- }
- };
+ /* Display a text input for searching through DATA.*/
+ Text_input.prototype.show = function show (data) {
+ if (!features || features.datalistelem)
+ {
+ var datalist = create_datalist (data);
+ datalist.setAttribute ("id", this.id + "-data");
+ this.data = data;
+ this.datalist = datalist;
+ this.element.appendChild (datalist);
+ }
+ this.element.removeAttribute ("hidden");
+ this.input.focus ();
+ };
- var index = new Text_input ("index");
- index.render = function (state) {
- if (state.text_input === "index")
- {
- if (state.index)
- this.show (state.index);
- else
- store.dispatch (actions.warn ("No index in this document"))
- }
- };
+ Text_input.prototype.hide = function hide () {
+ this.element.setAttribute ("hidden", "true");
+ this.input.value = "";
+ if (this.datalist)
+ {
+ this.datalist.remove ();
+ this.datalist = null;
+ }
+ };
- var search = new Search_input ("regexp-search");
- search.render = function (state) {
- if (state.text_input === "regexp-search")
- this.show ();
- };
+ function
+ Minibuffer ()
+ {
+ /* Create global container.*/
+ var elem = document.createElement ("div");
+ elem.classList.add ("text-input");
- elem.appendChild (menu.element);
- elem.appendChild (index.element);
- elem.appendChild (search.element);
-
- /* Create a container for warning when no menu in current page.*/
- var warn$ = document.createElement ("div");
- warn$.setAttribute ("hidden", "true");
- elem.appendChild (warn$);
-
- this.element = elem;
- this.menu = menu;
- this.index = index;
- this.search = search;
- this.warn = warn$;
- this.toid = null;
- }
+ var menu = new Text_input ("menu");
+ menu.render = function (state) {
+ if (state.text_input === "menu")
+ {
+ var current_menu = state.loaded_nodes[state.current].menu;
+ if (current_menu)
+ this.show (current_menu);
+ else
+ store.dispatch (actions.warn ("No menu in this node"));
+ }
+ };
- Minibuffer.prototype.render = function render (state) {
- if (!state.warning)
- {
- this.warn.setAttribute ("hidden", "true");
- this.toid = null;
- }
- else if (!this.toid)
- {
- console.warn (state.warning);
- var toid = window.setTimeout (function () {
- store.dispatch ({ type: "warning", msg: null });
- }, config.WARNING_TIMEOUT);
- this.warn.innerHTML = state.warning;
- this.warn.removeAttribute ("hidden");
- this.toid = toid;
- }
+ var index = new Text_input ("index");
+ index.render = function (state) {
+ if (state.text_input === "index")
+ {
+ if (state.index)
+ this.show (state.index);
+ else
+ store.dispatch (actions.warn ("No index in this document"))
+ }
+ };
- if (!state.text_input || state.warning)
- {
- this.menu.hide ();
- this.index.hide ();
- this.search.hide ();
- }
- else
- {
- this.index.render (state);
- this.menu.render (state);
- this.search.render (state);
- }
+ var search = new Search_input ("regexp-search");
+ search.render = function (state) {
+ if (state.text_input === "regexp-search")
+ this.show ();
};
- function
- Echo_area ()
- {
- var elem = document.createElement ("div");
- elem.classList.add ("echo-area");
- elem.setAttribute ("hidden", "true");
-
- this.element = elem;
- this.toid = null;
- }
-
- Echo_area.prototype.render = function render (state) {
- if (!state.echo)
- {
- this.element.setAttribute ("hidden", "true");
- this.toid = null;
- }
- else if (!this.toid)
- {
- console.info (state.echo);
- var toid = window.setTimeout (function () {
- store.dispatch ({ type: "echo", msg: null });
- }, config.WARNING_TIMEOUT);
- this.element.innerHTML = state.echo;
- this.element.removeAttribute ("hidden");
- this.toid = toid;
- }
- };
-
- /*----------------------------.
- | Component for the sidebar. |
- `----------------------------*/
-
- function
- Sidebar (contents_node)
- {
- this.element = document.createElement ("div"); // FIXME unneeded?
- this.element.setAttribute ("id", "slider");
- var div = document.createElement ("div");
- div.classList.add ("toc-sidebar");
- var toc = document.querySelector ("#SEC_Contents");
- toc.remove ();
+ elem.appendChild (menu.element);
+ elem.appendChild (index.element);
+ elem.appendChild (search.element);
+
+ /* Create a container for warning when no menu in current page.*/
+ var warn$ = document.createElement ("div");
+ warn$.setAttribute ("hidden", "true");
+ elem.appendChild (warn$);
+
+ this.element = elem;
+ this.menu = menu;
+ this.index = index;
+ this.search = search;
+ this.warn = warn$;
+ this.toid = null;
+ }
- // Like n.cloneNode, but also copy _href
- function cloneNode(n)
+ Minibuffer.prototype.render = function render (state) {
+ if (!state.warning)
{
- let r = n.cloneNode(false);
- for (let c = n.firstChild; c; c = c.nextSibling)
- {
- let d = cloneNode(c);
- let h = c._href;
- if (h)
- d._href = h;
- r.appendChild(d);
- }
- return r;
+ this.warn.setAttribute ("hidden", "true");
+ this.toid = null;
+ }
+ else if (!this.toid)
+ {
+ console.warn (state.warning);
+ var toid = window.setTimeout (function () {
+ store.dispatch ({ type: "warning", msg: null });
+ }, config.WARNING_TIMEOUT);
+ this.warn.innerHTML = state.warning;
+ this.warn.removeAttribute ("hidden");
+ this.toid = toid;
}
- contents_node.appendChild(cloneNode(toc));
-
- /* Remove table of contents header. */
- toc = toc.querySelector(".contents"); // skip ToC header
-
- /* Move contents of <body> into a a fresh <div> to let the components
- treat the index page like other iframe page. */
- var nav = document.createElement ("nav");
- nav.classList.add ("contents");
- for (var ch = toc.firstChild; ch; ch = toc.firstChild)
- nav.appendChild (ch);
-
- var div$ = document.createElement ("div");
- div$.classList.add ("toc");
- div$.appendChild (nav);
- div.appendChild (div$);
- this.element.appendChild (div);
-
- let header = document.createElement ("header");
- div$.parentElement.insertBefore (header, div$);
- let hider = document.createElement ("button");
- hider.classList.add ("sidebar-hider");
- hider.innerHTML = config.HIDE_SIDEBAR_HTML;
- this.show_sidebar_button = hider;
- header.appendChild(hider);
- }
-
- /* Render 'sidebar' according to STATE which is a new state. */
- Sidebar.prototype.render = function render (state) {
- /* Update sidebar to highlight the title corresponding to
- 'state.current'.*/
- let currently_showing = document.body.getAttribute("show-sidebar");
- let show = state.show_sidebar;
- if (show == "hide-if-narrow")
- show = is_narrow_window() || currently_showing == "no" ? "no" : "yes";
- if (show === undefined)
- show = "yes";
- if (show !== currently_showing)
- {
- document.body.setAttribute("show-sidebar", show);
- this.show_sidebar_button.innerHTML = show == "yes" ?
config.HIDE_SIDEBAR_HTML : config.SHOW_SIDEBAR_HTML;
- let tooltip = show == "yes" ? config.HIDE_SIDEBAR_TOOLTIP :
config.SHOW_SIDEBAR_TOOLTIP;
- if (tooltip)
- this.show_sidebar_button.setAttribute("title", tooltip);
- else
- this.show_sidebar_button.removeAttribute("title");
- }
- var msg = { message_kind: "update-sidebar", selected: state.current,
section_hash: state.section_hash };
- window.postMessage (msg, "*");
- };
-
- /*--------------------------.
- | Component for the pages. |
- `--------------------------*/
-
- /** @arg {HTMLDivElement} index_div */
- function
- Pages (index_div)
- {
- index_div.setAttribute ("id", config.INDEX_ID);
- index_div.setAttribute ("node", config.INDEX_ID);
- index_div.setAttribute ("hidden", "true");
- this.element = document.createElement ("div");
- this.element.setAttribute ("id", "sub-pages");
- this.element.appendChild (index_div);
- /** @type {string[]} Currently created divs. */
- this.ids = [config.INDEX_ID];
- /** @type {string} */
- this.prev_id = null;
- /** @type {HTMLElement} */
- this.prev_div = null;
- this.prev_search = null;
- this.main_title = document.title;
- }
-
- Pages.prototype.add_div = function add_div (pageid) {
- var div = document.createElement ("div");
- div.setAttribute ("id", pageid);
- div.setAttribute ("node", pageid);
- div.setAttribute ("hidden", "true");
- this.ids.push (pageid);
- this.element.appendChild (div);
- if (linkid_contains_index (pageid))
- load_page (pageid);
- return div;
- };
-
- Pages.prototype.render = function render (state) {
- var that = this;
-
- /* Create div elements for pages corresponding to newly added
- linkids from 'state.loaded_nodes'.*/
- Object.keys (state.loaded_nodes)
- .map (function (id) { return id.replace (/\..*$/, ""); })
- .filter (function (id) { return !that.ids.includes (id); })
- .reduce (function (acc, id) {
- return ((acc.includes (id)) ? acc : acc.concat ([id]));
- }, [])
- .forEach (function (id) { return that.add_div (id); });
-
- /* Blur pages if help screen is on. */
- this.element.classList[(state.help) ? "add" : "remove"] ("blurred");
- let div = this.prev_div;
- if (state.current !== this.prev_id)
- {
- if (this.prev_id)
- {
- this.prev_div.setAttribute ("hidden", "true");
- /* Remove previous highlights. */
- var old = linkid_split (this.prev_id);
- var msg = { message_kind: "highlight", regexp: null };
- post_message (old.pageid, msg);
- }
- div = resolve_page (state.current, true);
- update_history (state.current, state.history);
- this.prev_id = state.current;
- this.prev_div = div;
- }
- if (state.action.type === "window-title") {
- div.setAttribute("title", state.action.title);
- }
- let title = div.getAttribute("title") || this.main_title;
- if (title && document.title !== title)
- document.title = title;
-
- if (state.search
- && (this.prev_search !== state.search)
- && state.search.status === "ready")
- {
- this.prev_search = state.search;
- if (state.search.current_pageid === config.INDEX_ID)
- {
- window.setTimeout (function () {
- store.dispatch ({ type: "search-query" });
- var res = search (document.getElementById (config.INDEX_ID),
- state.search.regexp);
- store.dispatch ({ type: "search-result", found: res });
- }, 0);
- }
- else
- {
- window.setTimeout (function () {
- store.dispatch ({ type: "search-query" });
- var msg = {
- message_kind: "search",
- regexp: state.search.regexp,
- id: state.search.current_pageid
- };
- post_message (state.search.current_pageid, msg);
- }, 0);
- }
- }
-
- /* Update highlight of current page. */
- if (!state.highlight)
- {
- if (state.current === config.INDEX_ID)
- remove_highlight (document.getElementById (config.INDEX_ID));
- else
- {
- var link = linkid_split (state.current);
- var msg$ = { message_kind: "highlight", regexp: null };
- post_message (link.pageid, msg$);
- }
- }
-
- /* Scroll to highlighted search result. */
- if (state.search
- && (this.prev_search !== state.search)
- && state.search.status === "done"
- && state.search.found === true)
- {
- var link$ = linkid_split (state.current);
- var msg$$ = { message_kind: "scroll-to", hash: "#search-result" };
- post_message (link$.pageid, msg$$);
- this.prev_search = state.search;
- }
- };
-
- /*--------------.
- | Utilitaries. |
- `--------------*/
-
- /** Load PAGEID.
- @arg {string} pageid */
- function
- load_page (pageid)
- {
- var div = resolve_page (pageid, false);
- /* Making the iframe visible triggers the load of the iframe DOM. */
- if (div.hasAttribute ("hidden"))
- {
- div.removeAttribute ("hidden");
- div.setAttribute ("hidden", "true");
- }
- }
-
- /** Return the div element that correspond to LINKID. If VISIBLE is true
- then display the div and position the corresponding iframe to the
- anchor specified by LINKID.
- @arg {string} linkid - link identifier
- @arg {boolean} [visible]
- @return {HTMLElement} the div element. */
- function
- resolve_page (linkid, visible)
- {
- var msg;
- var link = linkid_split (linkid);
- var pageid = link.pageid;
- var div = document.getElementById (link.pageid);
- if (!div)
- {
- msg = "no iframe container correspond to identifier: " + pageid;
- throw new ReferenceError (msg);
- }
-
- /* Create iframe if necessary unless the div is refering to the Index
- or Contents page. */
- if (pageid === config.INDEX_ID || pageid === config.CONTENTS_ID)
- {
- div.removeAttribute ("hidden");
- /* Unlike iframes, Elements are unlikely to be scrollable (CSSOM
- Scroll-behavior), so choose an arbitrary element inside "index"
- div and at the top of it. */
- document.getElementById ("icon-bar").scrollIntoView ();
- }
- else
- {
- var iframe = div.querySelector ("iframe");
- if (!iframe)
- {
- iframe = document.createElement ("iframe");
- iframe.classList.add ("node");
- iframe.setAttribute ("src", linkid_to_url (pageid));
- div.appendChild (iframe);
- iframe.addEventListener ("load", function () {
- store.dispatch ({ type: "iframe-ready", id: pageid });
- }, false);
- }
- if (visible)
- {
- div.removeAttribute ("hidden");
- msg = { message_kind: "scroll-to", hash: link.hash };
- post_message (pageid, msg);
- }
- }
-
- return div;
- }
-
- /** Create a datalist element containing option elements corresponding
- to the keys in MENU. */
- function
- create_datalist (menu)
- {
- var datalist = document.createElement ("datalist");
- Object.keys (menu)
- .sort ()
- .forEach (function (title) {
- var opt = document.createElement ("option");
- opt.setAttribute ("value", title);
- datalist.appendChild (opt);
- });
- return datalist;
- }
-
- /** Mutate the history of page navigation. Store LINKID in history
- state, The actual way to store LINKID depends on HISTORY_MODE.
- @arg {string} linkid
- @arg {string} history_mode */
- function
- update_history (linkid, history_mode)
- {
- var method = window.history[history_mode];
- if (method)
- {
- /* Pretend that the current page is the one corresponding to the
- LINKID iframe. Handle errors since changing the visible file
- name can fail on some browsers with the "file:" protocol. */
- var visible_url =
- dirname (window.location.pathname) + linkid_to_url (linkid);
- try
- {
- method.call (window.history, linkid, null, visible_url);
- }
- catch (err)
- {
- /* Fallback to changing only the hash part which is safer. */
- visible_url = window.location.pathname;
- if (linkid !== config.INDEX_ID)
- visible_url += ("#" + linkid);
- method.call (window.history, linkid, null, visible_url);
- }
- }
- }
-
- /** Send MSG to the browsing context corresponding to PAGEID. This is a
- wrapper around 'Window.postMessage' which ensures that MSG is sent
- after PAGEID is loaded.
- @arg {string} pageid
- @arg {any} msg */
- function
- post_message (pageid, msg)
- {
- if (pageid === config.INDEX_ID || pageid === config.CONTENTS_ID)
- window.postMessage (msg, "*");
- else
- {
- load_page (pageid);
- var iframe = document.getElementById (pageid)
- .querySelector ("iframe");
- /* Semantically this would be better to use "promises" however
- they are not available in IE. */
- if (store.state.ready[pageid])
- iframe.contentWindow.postMessage (msg, "*");
- else
- {
- iframe.addEventListener ("load", function handler () {
- this.contentWindow.postMessage (msg, "*");
- this.removeEventListener ("load", handler, false);
- }, false);
- }
- }
- }
-
- /*--------------------------------------------
- | Event handlers for the top-level context. |
- `-------------------------------------------*/
-
- /* Initialize the top level 'config.INDEX_NAME' DOM. */
- function
- on_load ()
- {
- fix_links (document.links);
- add_icons ();
- document.body.classList.add ("mainbar");
-
- /* Move contents of <body> into a a fresh <div> to let the components
- treat the index page like other iframe page. */
- var index_div = document.createElement ("div");
- for (var ch = document.body.firstChild; ch; ch =
document.body.firstChild)
- index_div.appendChild (ch);
-
- /* Aggregation of all the sub-components. */
- var components = {
- element: document.body,
- components: [],
-
- add: function add (component) {
- this.components.push (component);
- this.element.appendChild (component.element);
- },
-
- render: function render (state) {
- this.components
- .forEach (function (cmpt) { cmpt.render (state); });
-
- /* Ensure that focus is on the current node unless some component is
- focused. */
- if (!state.focus)
- {
- var link = linkid_split (state.current);
- var elem = document.getElementById (link.pageid);
- if (link.pageid !== config.INDEX_ID && link.pageid !==
config.CONTENTS_ID)
- elem.querySelector ("iframe").focus ();
- else
- {
- /* Move the focus to top DOM. */
- document.documentElement.focus ();
- /* Allow the spacebar scroll in the main page to work. */
- elem.focus ();
- }
- }
- }
- };
- var pages = new Pages (index_div);
- components.add (pages);
- var contents_node = pages.add_div(config.CONTENTS_ID);
- components.add (new Sidebar (contents_node));
- components.add (new Help_page ());
- components.add (new Minibuffer ());
- components.add (new Echo_area ());
- store.listeners.push (components.render.bind (components));
-
- if (window.location.hash)
- {
- var linkid = normalize_hash (window.location.hash);
- store.dispatch (actions.set_current_url (linkid, "replaceState"));
- }
-
- /* Retrieve NEXT link and local menu. */
- var links = {};
- links[config.INDEX_ID] = navigation_links (document);
- store.dispatch (actions.cache_links (links));
- store.dispatch ({ type: "iframe-ready", id: config.INDEX_ID });
- store.dispatch ({
- type: "echo",
- msg: "Welcome to Texinfo documentation viewer 7.1.90, type '?' for
help."
- });
-
- /* Call user hook. */
- if (config.on_main_load)
- config.on_main_load ();
- }
-
- /* Handle messages received via the Message API.
- @arg {MessageEvent} event */
- function
- on_message (event)
- {
- var data = event.data;
- if (data.message_kind === "action")
- {
- /* Follow up actions to the store. */
- store.dispatch (data.action);
- }
- }
-
- /* Event handler for 'popstate' events. */
- function
- on_popstate (event)
- {
- /* When EVENT.STATE is 'null' it means that the user has manually
- changed the hash part of the URL bar. */
- var linkid = (event.state === null) ?
- normalize_hash (window.location.hash) : event.state;
- store.dispatch (actions.set_current_url (linkid, false));
- }
- return {
- on_load: on_load,
- on_message: on_message,
- on_popstate: on_popstate
- };
- }
+ if (!state.text_input || state.warning)
+ {
+ this.menu.hide ();
+ this.index.hide ();
+ this.search.hide ();
+ }
+ else
+ {
+ this.index.render (state);
+ this.menu.render (state);
+ this.search.render (state);
+ }
+ };
- /** Initialize the iframe which contains the lateral table of content. */
function
- init_sidebar ()
+ Echo_area ()
{
- /* Keep children but remove grandchildren (Exception: don't remove
- anything on the current page; however, that's not a problem in the Kawa
- manual). */
- function
- hide_grand_child_nodes (ul, excluded)
- {
- var lis = ul.children;
- for (var i = 0; i < lis.length; i += 1)
- {
- if (lis[i] === excluded)
- continue;
- var first = lis[i].firstElementChild;
- if (first && first.matches ("ul"))
- hide_grand_child_nodes (first);
- else if (first && first.matches ("a"))
- {
- var ul$ = first && first.nextElementSibling;
- if (ul$)
- ul$.setAttribute ("toc-detail", "yes");
- }
- }
- }
-
- /** Make the parent of ELEMS visible.
- @arg {HTMLElement} elem */
- function
- mark_parent_elements (elem)
- {
- if (elem && elem.parentElement && elem.parentElement.parentElement)
- {
- var pparent = elem.parentElement.parentElement;
- for (var sib = pparent.firstElementChild; sib;
- sib = sib.nextElementSibling)
- {
- if (sib !== elem.parentElement
- && sib.firstElementChild
- && sib.firstElementChild.nextElementSibling)
- {
- sib.firstElementChild
- .nextElementSibling
- .setAttribute ("toc-detail", "yes");
- }
- }
- }
- }
+ var elem = document.createElement ("div");
+ elem.classList.add ("echo-area");
+ elem.setAttribute ("hidden", "true");
- /** Scan ToC entries to see which should be hidden.
- @arg {HTMLElement} elem
- @arg {string} linkid */
- function
- scan_toc (elem, linkid, section_hash = null)
- {
- /** @type {Element} */
- var res;
- if (section_hash)
- {
- let dot = linkid.lastIndexOf('.');
- if (dot >= 0)
- linkid = linkid.substring(0, dot+1) + section_hash;
- }
- var url = with_sidebar_query (linkid_to_url (linkid));
+ this.element = elem;
+ this.toid = null;
+ }
- /** Set CURRENT to the node corresponding to URL linkid.
- @arg {Element} elem */
- function
- find_current (elem)
+ Echo_area.prototype.render = function render (state) {
+ if (!state.echo)
{
- if (elem.localName === "a" && link_href(elem) == url)
- {
- elem.setAttribute ("toc-current", "yes");
- var sib = elem.nextElementSibling;
- if (sib && sib.matches ("ul"))
- hide_grand_child_nodes (sib);
- res = elem;
- }
+ this.element.setAttribute ("hidden", "true");
+ this.toid = null;
}
-
- var ul = elem.querySelector ("ul");
- if (linkid === config.INDEX_ID || linkid === config.CONTENTS_ID)
- {
- hide_grand_child_nodes (ul);
- res = document.getElementById(linkid);
- }
- else
- {
- depth_first_walk (ul, find_current, Node.ELEMENT_NODE);
- /* Mark every parent node. */
- var current = res;
- while (current && current !== ul)
- {
- mark_parent_elements (current);
- /* XXX: Special case for manuals with '@part' commands. */
- if (current.parentElement === ul)
- hide_grand_child_nodes (ul, current);
- current = current.parentElement;
- }
- }
- return res;
- }
-
- /* Build the global dictionary containing navigation links from NAV. NAV
- must be an 'ul' DOM element containing the table of content of the
- manual. */
- function
- create_link_dict (nav)
- {
- var prev_id = config.INDEX_ID;
- var links = {};
-
- function
- add_link (elem)
+ else if (!this.toid)
{
- let href;
- if (elem.matches ("a") && (href = link_href(elem)))
- {
- var id = href_hash (href);
- links[prev_id] =
- Object.assign ({}, links[prev_id], { forward: id });
- links[id] = Object.assign ({}, links[id], { backward: prev_id });
- prev_id = id;
- }
+ console.info (state.echo);
+ var toid = window.setTimeout (function () {
+ store.dispatch ({ type: "echo", msg: null });
+ }, config.WARNING_TIMEOUT);
+ this.element.innerHTML = state.echo;
+ this.element.removeAttribute ("hidden");
+ this.toid = toid;
}
+ };
- depth_first_walk (nav, add_link, Node.ELEMENT_NODE);
- /* Add a reference to the first and last node of the manual. */
- links["*TOP*"] = config.INDEX_ID;
- links["*END*"] = prev_id;
- return links;
- }
-
- /* Add a link from the sidebar to the main index file. ELEM is the first
- sibling of the newly created header. */
- function
- add_header (elem)
- {
- var h1 = document.querySelector ("h1.settitle");
- if (!h1)
- h1 = document.querySelector ("h1.top");
- if (h1)
- {
- var a = document.createElement ("a");
- a.setAttribute ("href", config.INDEX_NAME);
- a.setAttribute ("id", config.INDEX_ID);
-
- let header = elem.previousSibling;
- header.appendChild (a);
- if (window.sidebarLinkAppendContents)
- window.sidebarLinkAppendContents(a, h1.textContent);
- else
- {
- var div = document.createElement ("div");
- a.appendChild (div);
- var span = document.createElement ("span");
- span.textContent = h1.textContent;
- div.appendChild (span);
- }
- }
- }
-
- /*------------------------------------------
- | Event handlers for the sidebar context. |
- `-----------------------------------------*/
-
- /* Initialize TOC_FILENAME which must be loaded in the context of an
- iframe. */
- function
- on_load ()
- {
- var toc_div = document.getElementById ("slider");
- add_header (toc_div.querySelector (".toc"));
-
- /* Specify the base URL to use for all relative URLs. */
- /* FIXME: Add base also for sub-pages. */
- var base = document.createElement ("base");
- base.setAttribute ("href",
- window.location.href.replace (/[/][^/]*$/, "/"));
- document.head.appendChild (base);
-
- scan_toc (toc_div, config.INDEX_NAME);
-
- /* Get 'backward' and 'forward' link attributes. */
- var dict = create_link_dict (toc_div.querySelector ("nav.contents ul"));
- store.dispatch (actions.cache_links (dict));
- }
-
- /* Handle messages received via the Message API. */
- function
- on_message (event)
- {
- var data = event.data;
- if (data.message_kind === "update-sidebar")
- {
- var toc_div = document.getElementById ("slider");
-
- /* Reset previous calls to 'scan_toc'. */
- depth_first_walk (toc_div, function clear_toc_styles (elem) {
- elem.removeAttribute ("toc-detail");
- elem.removeAttribute ("toc-current");
- }, Node.ELEMENT_NODE);
-
- /* Remove the hash part for the main page. */
- var pageid = linkid_split (data.selected).pageid;
- var selected = (pageid === config.INDEX_ID) ? pageid : data.selected;
- /* Highlight the current LINKID in the table of content. */
- var elem = scan_toc (toc_div, selected, data.section_hash);
- if (elem)
- elem.scrollIntoView (true);
- }
- }
-
- return {
- on_load: on_load,
- on_message: on_message
- };
- }
+ /*----------------------------.
+ | Component for the sidebar. |
+ `----------------------------*/
- /** Initialize iframes which contain pages of the manual. */
function
- init_iframe ()
+ Sidebar (contents_node)
{
- /* Initialize the DOM for generic pages loaded in the context of an
- iframe. */
- function
- on_load ()
- {
- document.body.classList.add ("in-iframe");
- fix_links (document.links);
- var links = {};
- var linkid = basename (window.location.pathname, /[.]x?html$/);
- links[linkid] = navigation_links (document);
- store.dispatch (actions.cache_links (links));
- if (document.title)
- store.dispatch (actions.window_title (document.title));
-
- if (linkid_contains_index (linkid))
- {
- /* Scan links that should be added to the index. */
- var index_entries = document.querySelectorAll
- ("td.printindex-index-entry");
- store.dispatch (actions.cache_index_links (index_entries));
- }
-
- add_icons ();
-
- /* Call user hook. */
- if (config.on_iframe_load)
- config.on_iframe_load ();
- }
+ this.element = document.createElement ("div"); // FIXME unneeded?
+ this.element.setAttribute ("id", "slider");
+ var div = document.createElement ("div");
+ div.classList.add ("toc-sidebar");
+ var toc = document.querySelector ("#SEC_Contents");
+ toc.remove ();
- /* Handle messages received via the Message API. */
- function
- on_message (event)
+ // Like n.cloneNode, but also copy _href
+ function cloneNode(n)
{
- var data = event.data;
- if (data.message_kind === "highlight")
- remove_highlight (document.body);
- else if (data.message_kind === "search")
- {
- var found = search (document.body, data.regexp);
- store.dispatch ({ type: "search-result", found: found });
- }
- else if (data.message_kind === "scroll-to")
+ let r = n.cloneNode(false);
+ for (let c = n.firstChild; c; c = c.nextSibling)
{
- /* Scroll to the anchor corresponding to HASH. */
- if (data.hash)
- {
- let elem = document.getElementById(data.hash.substring(1));
- // Check if hash reference is to a sectioing element.
- // If not we need to find the outer sectioning element,
- // so we can update the sidebar's ToC correctly.
- if (elem)
- {
- let p = elem;
- let section = null;
- let id = null;
- for (let p = elem;
- section === null && p instanceof Element;
- p = p.parentNode)
- {
- let sid = p.getAttribute("id");
- if (sid == null)
- continue;
- let cl = p.classList;
- for (let i = cl.length; --i >= 0; )
- {
- if (section_names.indexOf(cl.item(i)) >= 0)
- {
- section = p;
- id = sid;
- break;
- }
- }
- }
- if (section && section !== elem)
- {
- // Send section id to sidebar so it can properly
update.
- store.dispatch({ type: "section", hash: data.hash,
section_hash: id } );
- }
- }
- window.location.replace (data.hash);
- }
- else
- window.scroll (0, 0);
+ let d = cloneNode(c);
+ let h = c._href;
+ if (h)
+ d._href = h;
+ r.appendChild(d);
}
+ return r;
}
-
- return {
- on_load: on_load,
- on_message: on_message
- };
+ contents_node.appendChild(cloneNode(toc));
+
+ /* Remove table of contents header. */
+ toc = toc.querySelector(".contents"); // skip ToC header
+
+ /* Move contents of <body> into a a fresh <div> to let the components
+ treat the index page like other iframe page. */
+ var nav = document.createElement ("nav");
+ nav.classList.add ("contents");
+ for (var ch = toc.firstChild; ch; ch = toc.firstChild)
+ nav.appendChild (ch);
+
+ var div$ = document.createElement ("div");
+ div$.classList.add ("toc");
+ div$.appendChild (nav);
+ div.appendChild (div$);
+ this.element.appendChild (div);
+
+ let header = document.createElement ("header");
+ div$.parentElement.insertBefore (header, div$);
+ let hider = document.createElement ("button");
+ hider.classList.add ("sidebar-hider");
+ hider.innerHTML = config.HIDE_SIDEBAR_HTML;
+ this.show_sidebar_button = hider;
+ header.appendChild(hider);
}
- /*-------------------------
- | Common event handlers. |
- `------------------------*/
-
- /** Handle click events. */
- function
- on_click (event)
- {
- let in_sidebar = event.target.matches ("#slider *");
- for (var target = event.target; target !== null; target =
target.parentNode)
+ /* Render 'sidebar' according to STATE which is a new state. */
+ Sidebar.prototype.render = function render (state) {
+ /* Update sidebar to highlight the title corresponding to
+ 'state.current'.*/
+ let currently_showing = document.body.getAttribute("show-sidebar");
+ let show = state.show_sidebar;
+ if (show == "hide-if-narrow")
+ show = is_narrow_window() || currently_showing == "no" ? "no" : "yes";
+ if (show === undefined)
+ show = "yes";
+ if (show !== currently_showing)
{
- if (! (target instanceof Element))
- continue;
- if (target.matches ("a"))
- {
- var href = link_href(target);
- if (href && maybe_pageref_url_p (href)
- && !external_manual_url_p (href))
- {
- var linkid = href_hash (href) || config.INDEX_ID;
- if (linkid === "index.SEC_Contents")
- linkid = config.CONTENTS_ID;
- store.dispatch (actions.set_current_url (linkid, null,
- in_sidebar ?
"in-sidebar" : "in-page"));
- event.preventDefault ();
- event.stopPropagation ();
- break;
- }
- }
- if (target.matches (".sidebar-hider"))
- {
- let body = document.body;
- let show = body.getAttribute("show-sidebar");
- show_sidebar (show==="no");
- return;
- }
+ document.body.setAttribute("show-sidebar", show);
+ this.show_sidebar_button.innerHTML = show == "yes" ?
config.HIDE_SIDEBAR_HTML : config.SHOW_SIDEBAR_HTML;
+ let tooltip = show == "yes" ? config.HIDE_SIDEBAR_TOOLTIP :
config.SHOW_SIDEBAR_TOOLTIP;
+ if (tooltip)
+ this.show_sidebar_button.setAttribute("title", tooltip);
+ else
+ this.show_sidebar_button.removeAttribute("title");
}
- if (! in_sidebar)
- hide_sidebar_if_narrow ();
- }
-
- // Only valid when showing sidebar.
- function is_narrow_window ()
- {
- return document.body.firstChild.offsetLeft == 0;
- }
-
- function show_sidebar (show)
- {
- store.dispatch({ type: "show-sidebar", show: show ? "yes" : "no" });
- }
+ var msg = { message_kind: "update-sidebar", selected: state.current,
section_hash: state.section_hash };
+ window.postMessage (msg, "*");
+ };
- function hide_sidebar_if_narrow ()
- {
- store.dispatch({ type: "show-sidebar", show: "hide-if-narrow" });
- }
+ /*--------------------------.
+ | Component for the pages. |
+ `--------------------------*/
- /** Handle unload events. */
+ /** @arg {HTMLDivElement} index_div */
function
- on_unload ()
+ Pages (index_div)
{
- /* Cross origin requests are not supported in file protocol. */
- if (window.location.protocol !== "file:")
- {
- var request = new XMLHttpRequest ();
- request.open ("GET", "(WINDOW-CLOSED)");
- request.send (null);
- }
+ index_div.setAttribute ("id", config.INDEX_ID);
+ index_div.setAttribute ("node", config.INDEX_ID);
+ index_div.setAttribute ("hidden", "true");
+ this.element = document.createElement ("div");
+ this.element.setAttribute ("id", "sub-pages");
+ this.element.appendChild (index_div);
+ /** @type {string[]} Currently created divs. */
+ this.ids = [config.INDEX_ID];
+ /** @type {string} */
+ this.prev_id = null;
+ /** @type {HTMLElement} */
+ this.prev_div = null;
+ this.prev_search = null;
+ this.main_title = document.title;
}
- /** Handle Keyboard 'keydown' events.
- @arg {KeyboardEvent} event */
- function
- on_keydown (event)
- {
- if (is_escape_key (event.key))
- store.dispatch ({ type: "unfocus" });
- else
- {
- var val = on_keydown.dict[event.key];
- if (val)
- {
- if (typeof val === "function")
- val ();
- else
- store.dispatch (val);
- event.preventDefault();
- }
- }
- }
-
- /* Associate an Event 'key' property to an action or a thunk. */
- on_keydown.dict = {
- i: actions.show_text_input ("index"),
- l: window.history.back.bind (window.history),
- m: actions.show_text_input ("menu"),
- n: actions.navigate ("next"),
- p: actions.navigate ("prev"),
- r: window.history.forward.bind (window.history),
- s: actions.show_text_input ("regexp-search"),
- t: actions.set_current_url_pointer ("*TOP*"),
- u: actions.navigate ("up"),
- "]": actions.navigate ("forward"),
- "[": actions.navigate ("backward"),
- "<": actions.set_current_url_pointer ("*TOP*"),
- ">": actions.set_current_url_pointer ("*END*"),
- "?": actions.show_help ()
- };
-
- /** Some standard methods used in this script might not be implemented by
- the current browser. If that is the case then augment the prototypes
- appropriately. */
- function
- register_polyfills ()
- {
- function
- includes (search, start)
- {
- start = start || 0;
- return ((start + search.length) <= this.length)
- && (this.indexOf (search, start) !== -1);
- }
-
- /* eslint-disable no-extend-native */
- if (!Array.prototype.includes)
- Array.prototype.includes = includes;
-
- if (!String.prototype.includes)
- String.prototype.includes = includes;
+ Pages.prototype.add_div = function add_div (pageid) {
+ var div = document.createElement ("div");
+ div.setAttribute ("id", pageid);
+ div.setAttribute ("node", pageid);
+ div.setAttribute ("hidden", "true");
+ this.ids.push (pageid);
+ this.element.appendChild (div);
+ if (linkid_contains_index (pageid))
+ load_page (pageid);
+ return div;
+ };
- if (!String.prototype.endsWith)
+ Pages.prototype.render = function render (state) {
+ var that = this;
+
+ /* Create div elements for pages corresponding to newly added
+ linkids from 'state.loaded_nodes'.*/
+ Object.keys (state.loaded_nodes)
+ .map (function (id) { return id.replace (/\..*$/, ""); })
+ .filter (function (id) { return !that.ids.includes (id); })
+ .reduce (function (acc, id) {
+ return ((acc.includes (id)) ? acc : acc.concat ([id]));
+ }, [])
+ .forEach (function (id) { return that.add_div (id); });
+
+ /* Blur pages if help screen is on. */
+ this.element.classList[(state.help) ? "add" : "remove"] ("blurred");
+ let div = this.prev_div;
+ if (state.current !== this.prev_id)
{
- String.prototype.endsWith = function endsWith (search, position) {
- var subject_string = this.toString ();
- if (typeof position !== "number"
- || !isFinite (position)
- || Math.floor (position) !== position
- || position > subject_string.length)
- position = subject_string.length;
-
- position -= search.length;
- var last_index = subject_string.lastIndexOf (search, position);
- return last_index !== -1 && last_index === position;
- };
+ if (this.prev_id)
+ {
+ this.prev_div.setAttribute ("hidden", "true");
+ /* Remove previous highlights. */
+ var old = linkid_split (this.prev_id);
+ var msg = { message_kind: "highlight", regexp: null };
+ post_message (old.pageid, msg);
+ }
+ div = resolve_page (state.current, true);
+ update_history (state.current, state.history);
+ this.prev_id = state.current;
+ this.prev_div = div;
}
-
- if (!Element.prototype.matches)
- {
- Element.prototype.matches = Element.prototype["matchesSelector"]
- || Element.prototype["mozMatchesSelector"]
- || Element.prototype["mozMatchesSelector"]
- || Element.prototype["msMatchesSelector"]
- || Element.prototype["webkitMatchesSelector"]
- || function element_matches (str) {
- var document = (this.document || this.ownerDocument);
- var matches = document.querySelectorAll (str);
- var i = matches.length;
- while ((i -= 1) >= 0 && matches.item (i) !== this);
- return i > -1;
- };
+ if (state.action.type === "window-title") {
+ div.setAttribute("title", state.action.title);
}
+ let title = div.getAttribute("title") || this.main_title;
+ if (title && document.title !== title)
+ document.title = title;
- if (typeof Object.assign != "function")
+ if (state.search
+ && (this.prev_search !== state.search)
+ && state.search.status === "ready")
{
- Object.assign = function assign (target) {
- if (undef_or_null (target))
- throw new TypeError ("Cannot convert undefined or null to object");
-
- var to = Object (target);
- for (var index = 1; index < arguments.length; index += 1)
- {
- var next_source = arguments[index];
- if (undef_or_null (next_source))
- continue;
- for (var key in next_source)
- {
- /* Avoid bugs when hasOwnProperty is shadowed. */
- if (Object.prototype.hasOwnProperty.call (next_source, key))
- to[key] = next_source[key];
- }
- }
- return to;
- };
+ this.prev_search = state.search;
+ if (state.search.current_pageid === config.INDEX_ID)
+ {
+ window.setTimeout (function () {
+ store.dispatch ({ type: "search-query" });
+ var res = search (document.getElementById (config.INDEX_ID),
+ state.search.regexp);
+ store.dispatch ({ type: "search-result", found: res });
+ }, 0);
+ }
+ else
+ {
+ window.setTimeout (function () {
+ store.dispatch ({ type: "search-query" });
+ var msg = {
+ message_kind: "search",
+ regexp: state.search.regexp,
+ id: state.search.current_pageid
+ };
+ post_message (state.search.current_pageid, msg);
+ }, 0);
+ }
}
- (function (protos) {
- protos.forEach (function (proto) {
- if (!proto.hasOwnProperty ("remove"))
+ /* Update highlight of current page. */
+ if (!state.highlight)
+ {
+ if (state.current === config.INDEX_ID)
+ remove_highlight (document.getElementById (config.INDEX_ID));
+ else
{
- Object.defineProperty (proto, "remove", {
- configurable: true,
- enumerable: true,
- writable: true,
- value: function value () {
- this.parentNode.removeChild (this);
- }
- });
+ var link = linkid_split (state.current);
+ var msg$ = { message_kind: "highlight", regexp: null };
+ post_message (link.pageid, msg$);
}
- });
- } ([Element.prototype, CharacterData.prototype, DocumentType.prototype]));
- /* eslint-enable no-extend-native */
- }
+ }
- /*---------------------.
- | Common utilitaries. |
- `---------------------*/
+ /* Scroll to highlighted search result. */
+ if (state.search
+ && (this.prev_search !== state.search)
+ && state.search.status === "done"
+ && state.search.found === true)
+ {
+ var link$ = linkid_split (state.current);
+ var msg$$ = { message_kind: "scroll-to", hash: "#search-result" };
+ post_message (link$.pageid, msg$$);
+ this.prev_search = state.search;
+ }
+ };
- /** Check portably if KEY correspond to "Escape" key value.
- @arg {string} key */
- function
- is_escape_key (key)
- {
- /* In Internet Explorer 9 and Firefox 36 and earlier, the Esc key
- returns "Esc" instead of "Escape". */
- return key === "Escape" || key === "Esc";
- }
+ /*--------------.
+ | Utilitaries. |
+ `--------------*/
- /** Check if OBJ is equal to 'undefined' or 'null'. */
+ /** Load PAGEID.
+ @arg {string} pageid */
function
- undef_or_null (obj)
+ load_page (pageid)
{
- return (obj === null || typeof obj === "undefined");
+ var div = resolve_page (pageid, false);
+ /* Making the iframe visible triggers the load of the iframe DOM. */
+ if (div.hasAttribute ("hidden"))
+ {
+ div.removeAttribute ("hidden");
+ div.setAttribute ("hidden", "true");
+ }
}
- /** Return a relative URL corresponding to HREF, which refers to an anchor
- of 'config.INDEX_NAME'. HREF must be a USVString representing an
- absolute or relative URL. For example "foo/bar.html" will return
- "config.INDEX_NAME#bar".
-
- @arg {string} href.*/
+ /** Return the div element that correspond to LINKID. If VISIBLE is true
+ then display the div and position the corresponding iframe to the
+ anchor specified by LINKID.
+ @arg {string} linkid - link identifier
+ @arg {boolean} [visible]
+ @return {HTMLElement} the div element. */
function
- with_sidebar_query (href)
+ resolve_page (linkid, visible)
{
- if (basename (href) === config.INDEX_NAME)
- return config.INDEX_NAME;
- else
+ var msg;
+ var link = linkid_split (linkid);
+ var pageid = link.pageid;
+ var div = document.getElementById (link.pageid);
+ if (!div)
{
- /* XXX: Do not use the URL API for IE portability. */
- var url = with_sidebar_query.url;
- url.setAttribute ("href", href);
- var new_hash = "#" + basename (url.pathname, /[.]x?html/);
- /* XXX: 'new_hash !== url.hash' is a workaround to work with links
- produced by makeinfo which link to an anchor element in a page
- instead of directly to the page. */
- if (url.hash && new_hash !== url.hash)
- new_hash += ("." + url.hash.slice (1));
- return config.INDEX_NAME + new_hash;
+ msg = "no iframe container correspond to identifier: " + pageid;
+ throw new ReferenceError (msg);
}
- }
-
- /* Use the same DOM element for every function call. */
- with_sidebar_query.url = document.createElement ("a");
- /** Modify LINKS to handle the iframe based navigation properly. Relative
- links will be opened inside the corresponding iframe and absolute links
- will be opened in a new tab. If ID is true then define an "id"
- attribute with a linkid value for relative links.
-
- @typedef {HTMLAnchorElement|HTMLAreaElement} Links
- @arg {Links[]|HTMLCollectionOf<Links>} links
- @arg {boolean} [id]
- @return void */
- function
- fix_links (links, id)
- {
- for (var i = 0; i < links.length; i += 1)
+ /* Create iframe if necessary unless the div is refering to the Index
+ or Contents page. */
+ if (pageid === config.INDEX_ID || pageid === config.CONTENTS_ID)
{
- var link = links[i];
- var href = link_href(link);
- if (!href)
- continue;
- else if (external_manual_url_p (href))
- link.setAttribute ("target", "_top");
- else if (! maybe_pageref_url_p (href))
- link.setAttribute ("target", "_blank");
- else
+ div.removeAttribute ("hidden");
+ /* Unlike iframes, Elements are unlikely to be scrollable (CSSOM
+ Scroll-behavior), so choose an arbitrary element inside "index"
+ div and at the top of it. */
+ document.getElementById ("icon-bar").scrollIntoView ();
+ }
+ else
+ {
+ var iframe = div.querySelector ("iframe");
+ if (!iframe)
{
- var href$ = with_sidebar_query (href);
- if (href !== href$)
- {
- let protocol = location.protocol;
- if (protocol == "https:" || protocol == "http:")
- link._href = href$;
- else
- link.setAttribute ("href", href$);
- }
- if (id)
- {
- var linkid = (href$ === config.INDEX_NAME) ?
- config.INDEX_ID : href_hash (href$);
- link.setAttribute ("id", linkid);
- }
+ iframe = document.createElement ("iframe");
+ iframe.classList.add ("node");
+ iframe.setAttribute ("src", linkid_to_url (pageid));
+ div.appendChild (iframe);
+ iframe.addEventListener ("load", function () {
+ store.dispatch ({ type: "iframe-ready", id: pageid });
+ }, false);
+ }
+ if (visible)
+ {
+ div.removeAttribute ("hidden");
+ msg = { message_kind: "scroll-to", hash: link.hash };
+ post_message (pageid, msg);
}
}
+
+ return div;
}
- /** Convert HASH which is something that can be found 'Location.hash' to a
- "linkid" which can be handled in our model.
- @arg {string} hash
- @return {string} linkid */
+ /** Create a datalist element containing option elements corresponding
+ to the keys in MENU. */
function
- normalize_hash (hash)
+ create_datalist (menu)
{
- var text = hash.slice (1);
- /* Some anchor elements are present in 'config.INDEX_NAME' and we need to
- handle link to them specially (i.e. not try to find their corresponding
- iframe).*/
- if (config.MAIN_ANCHORS.includes (text))
- return config.INDEX_ID + "." + text;
- else
- return text;
+ var datalist = document.createElement ("datalist");
+ Object.keys (menu)
+ .sort ()
+ .forEach (function (title) {
+ var opt = document.createElement ("option");
+ opt.setAttribute ("value", title);
+ datalist.appendChild (opt);
+ });
+ return datalist;
}
- /** Return an object composed of the filename and the anchor of LINKID.
- LINKID can have the form "foobar.anchor" or just "foobar".
+ /** Mutate the history of page navigation. Store LINKID in history
+ state, The actual way to store LINKID depends on HISTORY_MODE.
@arg {string} linkid
- @return {{pageid: string, hash: string}} */
+ @arg {string} history_mode */
function
- linkid_split (linkid)
+ update_history (linkid, history_mode)
{
- if (!linkid.includes ("."))
- return { pageid: linkid, hash: "" };
- else
+ var method = window.history[history_mode];
+ if (method)
{
- var ref = linkid.match (/^(.+)\.(.*)$/).slice (1);
- return { pageid: ref[0], hash: "#" + ref[1] };
+ /* Pretend that the current page is the one corresponding to the
+ LINKID iframe. Handle errors since changing the visible file
+ name can fail on some browsers with the "file:" protocol. */
+ var visible_url =
+ dirname (window.location.pathname) + linkid_to_url (linkid);
+ try
+ {
+ method.call (window.history, linkid, null, visible_url);
+ }
+ catch (err)
+ {
+ /* Fallback to changing only the hash part which is safer. */
+ visible_url = window.location.pathname;
+ if (linkid !== config.INDEX_ID)
+ visible_url += ("#" + linkid);
+ method.call (window.history, linkid, null, visible_url);
+ }
}
}
- /** Convert LINKID which has the form "foobar.anchor" or just "foobar", to
- an URL of the form `foobar${config.EXT}#anchor`.
- @arg {string} linkid */
+ /** Send MSG to the browsing context corresponding to PAGEID. This is a
+ wrapper around 'Window.postMessage' which ensures that MSG is sent
+ after PAGEID is loaded.
+ @arg {string} pageid
+ @arg {any} msg */
function
- linkid_to_url (linkid)
+ post_message (pageid, msg)
{
- if (linkid === config.INDEX_ID)
- return config.INDEX_NAME;
+ if (pageid === config.INDEX_ID || pageid === config.CONTENTS_ID)
+ window.postMessage (msg, "*");
else
{
- var link = linkid_split (linkid);
- return link.pageid + config.EXT + link.hash;
+ load_page (pageid);
+ var iframe = document.getElementById (pageid)
+ .querySelector ("iframe");
+ /* Semantically this would be better to use "promises" however
+ they are not available in IE. */
+ if (store.state.ready[pageid])
+ iframe.contentWindow.postMessage (msg, "*");
+ else
+ {
+ iframe.addEventListener ("load", function handler () {
+ this.contentWindow.postMessage (msg, "*");
+ this.removeEventListener ("load", handler, false);
+ }, false);
+ }
}
}
- /** Check if 'URL' may be a cross-reference to another page in this manual.
*/
- function
- maybe_pageref_url_p (url)
- {
- return url.match(config.LOCAL_HTML_PAGE_PATTERN);
- }
+ /*--------------------------------------------
+ | Event handlers for the top-level context. |
+ `-------------------------------------------*/
- /** Check if 'URL' is a link to another manual. For locally installed
- manuals only. */
+ /* Initialize the top level 'config.INDEX_NAME' DOM. */
function
- external_manual_url_p (url)
+ on_load ()
{
- if (typeof url !== "string")
- throw new TypeError ("'" + url + "' is not a string");
+ fix_links (document.links);
+ add_icons ();
+ document.body.classList.add ("mainbar");
+
+ /* Move contents of <body> into a a fresh <div> to let the components
+ treat the index page like other iframe page. */
+ var index_div = document.createElement ("div");
+ for (var ch = document.body.firstChild; ch; ch = document.body.firstChild)
+ index_div.appendChild (ch);
+
+ /* Aggregation of all the sub-components. */
+ var components = {
+ element: document.body,
+ components: [],
+
+ add: function add (component) {
+ this.components.push (component);
+ this.element.appendChild (component.element);
+ },
+
+ render: function render (state) {
+ this.components
+ .forEach (function (cmpt) { cmpt.render (state); });
+
+ /* Ensure that focus is on the current node unless some component is
+ focused. */
+ if (!state.focus)
+ {
+ var link = linkid_split (state.current);
+ var elem = document.getElementById (link.pageid);
+ if (link.pageid !== config.INDEX_ID && link.pageid !==
config.CONTENTS_ID)
+ elem.querySelector ("iframe").focus ();
+ else
+ {
+ /* Move the focus to top DOM. */
+ document.documentElement.focus ();
+ /* Allow the spacebar scroll in the main page to work. */
+ elem.focus ();
+ }
+ }
+ }
+ };
+ var pages = new Pages (index_div);
+ components.add (pages);
+ var contents_node = pages.add_div(config.CONTENTS_ID);
+ components.add (new Sidebar (contents_node));
+ components.add (new Help_page ());
+ components.add (new Minibuffer ());
+ components.add (new Echo_area ());
+ store.listeners.push (components.render.bind (components));
+
+ if (window.location.hash)
+ {
+ var linkid = normalize_hash (window.location.hash);
+ store.dispatch (actions.set_current_url (linkid, "replaceState"));
+ }
- return url.match(/^..\//);
+ /* Retrieve NEXT link and local menu. */
+ var links = {};
+ links[config.INDEX_ID] = navigation_links (document);
+ store.dispatch (actions.cache_links (links));
+ store.dispatch ({ type: "iframe-ready", id: config.INDEX_ID });
+ store.dispatch ({
+ type: "echo",
+ msg: "Welcome to Texinfo documentation viewer 7.1.90, type '?' for help."
+ });
+
+ /* Call user hook. */
+ if (config.on_main_load)
+ config.on_main_load ();
}
- /** Return PATHNAME with any leading directory components removed. If
- specified, also remove a trailing SUFFIX. */
+ /* Handle messages received via the Message API.
+ @arg {MessageEvent} event */
function
- basename (pathname, suffix)
+ on_message (event)
{
- var res = pathname.replace (/.*[/]/, "");
- if (!suffix)
- return res;
- else if (suffix instanceof RegExp)
- return res.replace (suffix, "");
- else /* typeof SUFFIX === "string" */
- return res.replace (new RegExp ("[.]" + suffix), "");
+ var data = event.data;
+ if (data.message_kind === "action")
+ {
+ /* Follow up actions to the store. */
+ store.dispatch (data.action);
+ }
}
- /** Strip last component from PATHNAME and keep the trailing slash. For
- example if PATHNAME is "/foo/bar/baz.html" then return "/foo/bar/".
- @arg {string} pathname */
+ /* Event handler for 'popstate' events. */
function
- dirname (pathname)
+ on_popstate (event)
{
- var res = pathname.match (/\/?.*\//);
- if (res)
- return res[0];
- else
- throw new Error ("'location.pathname' is not recognized");
+ /* When EVENT.STATE is 'null' it means that the user has manually
+ changed the hash part of the URL bar. */
+ var linkid = (event.state === null) ?
+ normalize_hash (window.location.hash) : event.state;
+ store.dispatch (actions.set_current_url (linkid, false));
}
- /** Apply FUNC to each nodes in the NODE subtree. The walk follows a depth
- first algorithm. Only consider the nodes of type NODE_TYPE. */
+ return {
+ on_load: on_load,
+ on_message: on_message,
+ on_popstate: on_popstate
+ };
+}
+
+/** Initialize the iframe which contains the lateral table of content. */
+function
+init_sidebar ()
+{
+ /* Keep children but remove grandchildren (Exception: don't remove
+ anything on the current page; however, that's not a problem in the Kawa
+ manual). */
function
- depth_first_walk (node, func, node_type)
+ hide_grand_child_nodes (ul, excluded)
{
- if (!node_type || (node.nodeType === node_type))
- func (node);
-
- for (var child = node.firstChild; child; child = child.nextSibling)
- depth_first_walk (child, func, node_type);
+ var lis = ul.children;
+ for (var i = 0; i < lis.length; i += 1)
+ {
+ if (lis[i] === excluded)
+ continue;
+ var first = lis[i].firstElementChild;
+ if (first && first.matches ("ul"))
+ hide_grand_child_nodes (first);
+ else if (first && first.matches ("a"))
+ {
+ var ul$ = first && first.nextElementSibling;
+ if (ul$)
+ ul$.setAttribute ("toc-detail", "yes");
+ }
+ }
}
- /** Return the "effective" href attribute of a link (a) element. */
+ /** Make the parent of ELEMS visible.
+ @arg {HTMLElement} elem */
function
- link_href (alink)
+ mark_parent_elements (elem)
{
- return alink._href || alink.getAttribute("href");
+ if (elem && elem.parentElement && elem.parentElement.parentElement)
+ {
+ var pparent = elem.parentElement.parentElement;
+ for (var sib = pparent.firstElementChild; sib;
+ sib = sib.nextElementSibling)
+ {
+ if (sib !== elem.parentElement
+ && sib.firstElementChild
+ && sib.firstElementChild.nextElementSibling)
+ {
+ sib.firstElementChild
+ .nextElementSibling
+ .setAttribute ("toc-detail", "yes");
+ }
+ }
+ }
}
- /** Return the hash part of HREF without the '#' prefix. HREF must be a
- string. If there is no hash part in HREF then return the empty
- string. */
+ /** Scan ToC entries to see which should be hidden.
+ @arg {HTMLElement} elem
+ @arg {string} linkid */
function
- href_hash (href)
+ scan_toc (elem, linkid, section_hash = null)
{
- if (typeof href !== "string")
- throw new TypeError (href + " is not a string");
+ /** @type {Element} */
+ var res;
+ if (section_hash)
+ {
+ let dot = linkid.lastIndexOf('.');
+ if (dot >= 0)
+ linkid = linkid.substring(0, dot+1) + section_hash;
+ }
+ var url = with_sidebar_query (linkid_to_url (linkid));
- if (href.includes ("#"))
- return href.replace (/.*#/, "");
+ /** Set CURRENT to the node corresponding to URL linkid.
+ @arg {Element} elem */
+ function
+ find_current (elem)
+ {
+ if (elem.localName === "a" && link_href(elem) == url)
+ {
+ elem.setAttribute ("toc-current", "yes");
+ var sib = elem.nextElementSibling;
+ if (sib && sib.matches ("ul"))
+ hide_grand_child_nodes (sib);
+ res = elem;
+ }
+ }
+
+ var ul = elem.querySelector ("ul");
+ if (linkid === config.INDEX_ID || linkid === config.CONTENTS_ID)
+ {
+ hide_grand_child_nodes (ul);
+ res = document.getElementById(linkid);
+ }
else
- return "";
+ {
+ depth_first_walk (ul, find_current, Node.ELEMENT_NODE);
+ /* Mark every parent node. */
+ var current = res;
+ while (current && current !== ul)
+ {
+ mark_parent_elements (current);
+ /* XXX: Special case for manuals with '@part' commands. */
+ if (current.parentElement === ul)
+ hide_grand_child_nodes (ul, current);
+ current = current.parentElement;
+ }
+ }
+ return res;
}
- /** Check if LINKID corresponds to a page containing index links. */
+ /* Build the global dictionary containing navigation links from NAV. NAV
+ must be an 'ul' DOM element containing the table of content of the
+ manual. */
function
- linkid_contains_index (linkid)
+ create_link_dict (nav)
{
- return linkid.match (/^.*-index$/i) || linkid.match (/^Index$/);
+ var prev_id = config.INDEX_ID;
+ var links = {};
+
+ function
+ add_link (elem)
+ {
+ let href;
+ if (elem.matches ("a") && (href = link_href(elem)))
+ {
+ var id = href_hash (href);
+ links[prev_id] =
+ Object.assign ({}, links[prev_id], { forward: id });
+ links[id] = Object.assign ({}, links[id], { backward: prev_id });
+ prev_id = id;
+ }
+ }
+
+ depth_first_walk (nav, add_link, Node.ELEMENT_NODE);
+ /* Add a reference to the first and last node of the manual. */
+ links["*TOP*"] = config.INDEX_ID;
+ links["*END*"] = prev_id;
+ return links;
}
- /** Retrieve PREV, NEXT, and UP links, and local menu from CONTENT and
- return an object containing references to those links. CONTENT must be
- an object implementing the ParentNode interface (Element,
- Document...). */
+ /* Add a link from the sidebar to the main index file. ELEM is the first
+ sibling of the newly created header. */
function
- navigation_links (content)
+ add_header (elem)
{
- var links = content.querySelectorAll ("a[accesskey][href]");
- var res = {};
- /* links have the form MAIN_FILE.html#FRAME-ID. For convenience
- only store FRAME-ID. */
- for (var i = 0; i < links.length; i += 1)
+ var h1 = document.querySelector ("h1.settitle");
+ if (!h1)
+ h1 = document.querySelector ("h1.top");
+ if (h1)
{
- var link = links[i];
- var nav_id = navigation_links.dict[link.getAttribute ("accesskey")];
- if (nav_id)
- {
- var href = basename (link_href (link));
- if (href === config.INDEX_NAME)
- res[nav_id] = config.INDEX_ID;
- else
- res[nav_id] = href_hash (href);
- }
- else /* this link is part of local table of content. */
+ var a = document.createElement ("a");
+ a.setAttribute ("href", config.INDEX_NAME);
+ a.setAttribute ("id", config.INDEX_ID);
+
+ let header = elem.previousSibling;
+ header.appendChild (a);
+ if (window.sidebarLinkAppendContents)
+ window.sidebarLinkAppendContents(a, h1.textContent);
+ else
{
- res.menu = res.menu || {};
- res.menu[link.text] = href_hash (link_href (link));
+ var div = document.createElement ("div");
+ a.appendChild (div);
+ var span = document.createElement ("span");
+ span.textContent = h1.textContent;
+ div.appendChild (span);
}
}
-
- return res;
}
- navigation_links.dict = { n: "next", p: "prev", u: "up" };
+ /*------------------------------------------
+ | Event handlers for the sidebar context. |
+ `-----------------------------------------*/
+ /* Initialize TOC_FILENAME which must be loaded in the context of an
+ iframe. */
function
- add_icons ()
+ on_load ()
{
- var div = document.createElement ("div");
- div.setAttribute ("id", "icon-bar");
- var span = document.createElement ("span");
- span.innerHTML = "?";
- // Set tool-tip (on hover)
- span.setAttribute ("title", "Help for keyboard shortcuts");
- span.classList.add ("icon");
- span.addEventListener ("click", function () {
- store.dispatch (actions.show_help ());
- }, false);
- div.appendChild (span);
- document.body.insertBefore (div, document.body.firstChild);
- }
+ var toc_div = document.getElementById ("slider");
+ add_header (toc_div.querySelector (".toc"));
- /** Check if ELEM matches SEARCH
- @arg {Element} elem
- @arg {RegExp} rgxp
- @return {boolean} */
- function
- search (elem, rgxp)
- {
- /** @type {Text} */
- var text = null;
+ /* Specify the base URL to use for all relative URLs. */
+ /* FIXME: Add base also for sub-pages. */
+ var base = document.createElement ("base");
+ base.setAttribute ("href",
+ window.location.href.replace (/[/][^/]*$/, "/"));
+ document.head.appendChild (base);
- /** @arg {Text} node */
- function
- find (node)
- {
- if (rgxp.test (node.textContent))
- {
- /* Ignore previous match. */
- var prev = node.parentElement.matches ("span.highlight");
- text = (prev) ? null : (text || node);
- }
- }
+ scan_toc (toc_div, config.INDEX_NAME);
- depth_first_walk (elem, find, Node.TEXT_NODE);
- remove_highlight (elem);
- if (!text)
- return false;
- else
- {
- highlight_text (rgxp, text);
- return true;
- }
+ /* Get 'backward' and 'forward' link attributes. */
+ var dict = create_link_dict (toc_div.querySelector ("nav.contents ul"));
+ store.dispatch (actions.cache_links (dict));
}
- /** Find the pageid corresponding to forward direction.
- @arg {any} state
- @arg {string} linkid
- @return {string} the forward pageid */
+ /* Handle messages received via the Message API. */
function
- forward_pageid (state, linkid)
+ on_message (event)
{
- var data = state.loaded_nodes[linkid];
- if (!data)
- throw new Error ("page not loaded: " + linkid);
- else if (!data.forward)
- return null;
- else
+ var data = event.data;
+ if (data.message_kind === "update-sidebar")
{
- var cur = linkid_split (linkid);
- var fwd = linkid_split (data.forward);
- if (!fwd.hash && fwd.pageid !== cur.pageid)
- return fwd.pageid;
- else
- return forward_pageid (state, data.forward);
+ var toc_div = document.getElementById ("slider");
+
+ /* Reset previous calls to 'scan_toc'. */
+ depth_first_walk (toc_div, function clear_toc_styles (elem) {
+ elem.removeAttribute ("toc-detail");
+ elem.removeAttribute ("toc-current");
+ }, Node.ELEMENT_NODE);
+
+ /* Remove the hash part for the main page. */
+ var pageid = linkid_split (data.selected).pageid;
+ var selected = (pageid === config.INDEX_ID) ? pageid : data.selected;
+ /* Highlight the current LINKID in the table of content. */
+ var elem = scan_toc (toc_div, selected, data.section_hash);
+ if (elem)
+ elem.scrollIntoView (true);
}
}
- /** Highlight text in NODE which match RGXP.
- @arg {RegExp} rgxp
- @arg {Text} node */
+ return {
+ on_load: on_load,
+ on_message: on_message
+ };
+}
+
+/** Initialize iframes which contain pages of the manual. */
+function
+init_iframe ()
+{
+ /* Initialize the DOM for generic pages loaded in the context of an
+ iframe. */
function
- highlight_text (rgxp, node)
+ on_load ()
{
- /* Skip elements corresponding to highlighted words to avoid infinite
- recursion. */
- if (node.parentElement.matches ("span.highlight"))
- return;
-
- var matches = rgxp.exec (node.textContent);
- if (matches)
+ document.body.classList.add ("in-iframe");
+ fix_links (document.links);
+ var links = {};
+ var linkid = basename (window.location.pathname, /[.]x?html$/);
+ links[linkid] = navigation_links (document);
+ store.dispatch (actions.cache_links (links));
+ if (document.title)
+ store.dispatch (actions.window_title (document.title));
+
+ if (linkid_contains_index (linkid))
{
- /* Create an highlighted element containing first match. */
- var span = document.createElement ("span");
- span.appendChild (document.createTextNode (matches[0]));
- span.setAttribute ("id", "search-result");
- span.classList.add ("highlight");
-
- var right_node = node.splitText (matches.index);
- /* Remove first match from right node. */
- right_node.textContent = right_node.textContent
- .slice (matches[0].length);
- node.parentElement.insertBefore (span, right_node);
+ /* Scan links that should be added to the index. */
+ var index_entries = document.querySelectorAll
+ ("td.printindex-index-entry");
+ store.dispatch (actions.cache_index_links (index_entries));
}
+
+ add_icons ();
+
+ /* Call user hook. */
+ if (config.on_iframe_load)
+ config.on_iframe_load ();
}
- /** Remove every highlighted elements and inline their text content.
- @arg {Element} elem */
+ /* Handle messages received via the Message API. */
function
- remove_highlight (elem)
+ on_message (event)
{
- var spans = elem.getElementsByClassName ("highlight");
- /* Replace spans with their inner text node. */
- while (spans.length > 0)
+ var data = event.data;
+ if (data.message_kind === "highlight")
+ remove_highlight (document.body);
+ else if (data.message_kind === "search")
+ {
+ var found = search (document.body, data.regexp);
+ store.dispatch ({ type: "search-result", found: found });
+ }
+ else if (data.message_kind === "scroll-to")
{
- var span = spans[0];
- var parent = span.parentElement;
- parent.replaceChild (span.firstChild, span);
+ /* Scroll to the anchor corresponding to HASH. */
+ if (data.hash)
+ {
+ let elem = document.getElementById(data.hash.substring(1));
+ // Check if hash reference is to a sectioing element.
+ // If not we need to find the outer sectioning element,
+ // so we can update the sidebar's ToC correctly.
+ if (elem)
+ {
+ let p = elem;
+ let section = null;
+ let id = null;
+ for (let p = elem;
+ section === null && p instanceof Element;
+ p = p.parentNode)
+ {
+ let sid = p.getAttribute("id");
+ if (sid == null)
+ continue;
+ let cl = p.classList;
+ for (let i = cl.length; --i >= 0; )
+ {
+ if (section_names.indexOf(cl.item(i)) >= 0)
+ {
+ section = p;
+ id = sid;
+ break;
+ }
+ }
+ }
+ if (section && section !== elem)
+ {
+ // Send section id to sidebar so it can properly update.
+ store.dispatch({ type: "section", hash: data.hash,
section_hash: id } );
+ }
+ }
+ window.location.replace (data.hash);
+ }
+ else
+ window.scroll (0, 0);
}
}
- /*--------------.
- | Entry point. |
- `--------------*/
-
- /** Depending on the role of the document launching this script, different
- event handlers are registered. This script can be used in the context
of:
-
- - the index page of the manual which manages the state of the application
- - the iframe which contains the lateral table of content
- - other iframes which contain other pages of the manual
-
- This is done to allow referencing the same script inside every HTML page.
- This has the benefits of reducing the number of HTTP requests required to
- fetch the Javascript code and simplifying the work of the Texinfo HTML
- converter. */
-
- /* Display MSG bail out message portably. */
+ return {
+ on_load: on_load,
+ on_message: on_message
+ };
+}
+
+/*-------------------------
+| Common event handlers. |
+`------------------------*/
+
+/** Handle click events. */
+function
+on_click (event)
+{
+ let in_sidebar = event.target.matches ("#slider *");
+ for (var target = event.target; target !== null; target = target.parentNode)
+ {
+ if (! (target instanceof Element))
+ continue;
+ if (target.matches ("a"))
+ {
+ var href = link_href(target);
+ if (href && maybe_pageref_url_p (href)
+ && !external_manual_url_p (href))
+ {
+ var linkid = href_hash (href) || config.INDEX_ID;
+ if (linkid === "index.SEC_Contents")
+ linkid = config.CONTENTS_ID;
+ store.dispatch (actions.set_current_url (linkid, null,
+ in_sidebar ?
"in-sidebar" : "in-page"));
+ event.preventDefault ();
+ event.stopPropagation ();
+ break;
+ }
+ }
+ if (target.matches (".sidebar-hider"))
+ {
+ let body = document.body;
+ let show = body.getAttribute("show-sidebar");
+ show_sidebar (show==="no");
+ return;
+ }
+ }
+ if (! in_sidebar)
+ hide_sidebar_if_narrow ();
+}
+
+// Only valid when showing sidebar.
+function is_narrow_window ()
+{
+ return document.body.firstChild.offsetLeft == 0;
+}
+
+function show_sidebar (show)
+{
+ store.dispatch({ type: "show-sidebar", show: show ? "yes" : "no" });
+}
+
+function hide_sidebar_if_narrow ()
+{
+ store.dispatch({ type: "show-sidebar", show: "hide-if-narrow" });
+}
+
+/** Handle unload events. */
+function
+on_unload ()
+{
+ /* Cross origin requests are not supported in file protocol. */
+ if (window.location.protocol !== "file:")
+ {
+ var request = new XMLHttpRequest ();
+ request.open ("GET", "(WINDOW-CLOSED)");
+ request.send (null);
+ }
+}
+
+/** Handle Keyboard 'keydown' events.
+ @arg {KeyboardEvent} event */
+function
+on_keydown (event)
+{
+ if (is_escape_key (event.key))
+ store.dispatch ({ type: "unfocus" });
+ else
+ {
+ var val = on_keydown.dict[event.key];
+ if (val)
+ {
+ if (typeof val === "function")
+ val ();
+ else
+ store.dispatch (val);
+ event.preventDefault();
+ }
+ }
+}
+
+/* Associate an Event 'key' property to an action or a thunk. */
+on_keydown.dict = {
+ i: actions.show_text_input ("index"),
+ l: window.history.back.bind (window.history),
+ m: actions.show_text_input ("menu"),
+ n: actions.navigate ("next"),
+ p: actions.navigate ("prev"),
+ r: window.history.forward.bind (window.history),
+ s: actions.show_text_input ("regexp-search"),
+ t: actions.set_current_url_pointer ("*TOP*"),
+ u: actions.navigate ("up"),
+ "]": actions.navigate ("forward"),
+ "[": actions.navigate ("backward"),
+ "<": actions.set_current_url_pointer ("*TOP*"),
+ ">": actions.set_current_url_pointer ("*END*"),
+ "?": actions.show_help ()
+};
+
+/** Some standard methods used in this script might not be implemented by
+ the current browser. If that is the case then augment the prototypes
+ appropriately. */
+function
+register_polyfills ()
+{
function
- error (msg)
+ includes (search, start)
{
- /* XXX: This code needs to be highly portable.
- Check <https://quirksmode.org/dom/core/> for details. */
- return function () {
- var div = document.createElement ("div");
- div.setAttribute ("class", "error");
- div.innerHTML = msg;
- var elem = document.body.firstChild;
- document.body.insertBefore (div, elem);
- window.setTimeout (function () {
- document.body.removeChild (div);
- }, config.WARNING_TIMEOUT);
- };
+ start = start || 0;
+ return ((start + search.length) <= this.length)
+ && (this.indexOf (search, start) !== -1);
}
- /* Check if current browser supports the minimum requirements required for
- properly using this script, otherwise bails out. */
- if (features && !(features.es5
- && features.classlist
- && features.eventlistener
- && features.hidden
- && features.history
- && features.postmessage
- && features.queryselector))
+ /* eslint-disable no-extend-native */
+ if (!Array.prototype.includes)
+ Array.prototype.includes = includes;
+
+ if (!String.prototype.includes)
+ String.prototype.includes = includes;
+
+ if (!String.prototype.endsWith)
{
- window.onload = error ("'info.js' is not compatible with this browser");
- return;
+ String.prototype.endsWith = function endsWith (search, position) {
+ var subject_string = this.toString ();
+ if (typeof position !== "number"
+ || !isFinite (position)
+ || Math.floor (position) !== position
+ || position > subject_string.length)
+ position = subject_string.length;
+
+ position -= search.length;
+ var last_index = subject_string.lastIndexOf (search, position);
+ return last_index !== -1 && last_index === position;
+ };
}
- /* Until we have a responsive design implemented, fallback to basic
- HTML navigation for small screen. */
- /*
- if (window.screen.availWidth < config.SCREEN_MIN_WIDTH)
+ if (!Element.prototype.matches)
{
- window.onload =
- error ("screen width is too small to display the table of content");
- return;
+ Element.prototype.matches = Element.prototype["matchesSelector"]
+ || Element.prototype["mozMatchesSelector"]
+ || Element.prototype["mozMatchesSelector"]
+ || Element.prototype["msMatchesSelector"]
+ || Element.prototype["webkitMatchesSelector"]
+ || function element_matches (str) {
+ var document = (this.document || this.ownerDocument);
+ var matches = document.querySelectorAll (str);
+ var i = matches.length;
+ while ((i -= 1) >= 0 && matches.item (i) !== this);
+ return i > -1;
+ };
}
-*/
-
- register_polyfills ();
- /* Let the config provided by the user mask the default one. */
- config = Object.assign (config, user_config);
- var inside_iframe = top !== window;
- var inside_index_page = window.location.pathname === config.INDEX_NAME
- || window.location.pathname.endsWith ("/" + config.INDEX_NAME)
- || window.location.pathname.endsWith ("/");
-
- if (inside_index_page)
+ if (typeof Object.assign != "function")
{
- var initial_state = {
- /* Dictionary associating page ids to next, prev, up, forward,
- backward link ids. */
- loaded_nodes: {},
- /* Dictionary associating keyword to linkids. */
- index: undefined,
- /* page id of the current page. */
- current: config.INDEX_ID,
- /* dictionary associating a page id to a boolean. */
- ready: {},
- /* Current mode for handling history. */
- history: "replaceState",
- /* Define the name of current text input. */
- text_input: null
+ Object.assign = function assign (target) {
+ if (undef_or_null (target))
+ throw new TypeError ("Cannot convert undefined or null to object");
+
+ var to = Object (target);
+ for (var index = 1; index < arguments.length; index += 1)
+ {
+ var next_source = arguments[index];
+ if (undef_or_null (next_source))
+ continue;
+ for (var key in next_source)
+ {
+ /* Avoid bugs when hasOwnProperty is shadowed. */
+ if (Object.prototype.hasOwnProperty.call (next_source, key))
+ to[key] = next_source[key];
+ }
+ }
+ return to;
};
+ }
- store = new Store (updater, initial_state);
- var index = init_index_page ();
- var sidebar = init_sidebar ();
- window.addEventListener ("DOMContentLoaded", function () {
- index.on_load ();
- sidebar.on_load ();
- }, false);
- window.addEventListener ("message", index.on_message, false);
- window.addEventListener ("message", sidebar.on_message, false);
- window.onpopstate = index.on_popstate;
+ (function (protos) {
+ protos.forEach (function (proto) {
+ if (!proto.hasOwnProperty ("remove"))
+ {
+ Object.defineProperty (proto, "remove", {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: function value () {
+ this.parentNode.removeChild (this);
+ }
+ });
+ }
+ });
+ } ([Element.prototype, CharacterData.prototype, DocumentType.prototype]));
+ /* eslint-enable no-extend-native */
+}
+
+/*---------------------.
+| Common utilitaries. |
+`---------------------*/
+
+/** Check portably if KEY correspond to "Escape" key value.
+ @arg {string} key */
+function
+is_escape_key (key)
+{
+ /* In Internet Explorer 9 and Firefox 36 and earlier, the Esc key
+ returns "Esc" instead of "Escape". */
+ return key === "Escape" || key === "Esc";
+}
+
+/** Check if OBJ is equal to 'undefined' or 'null'. */
+function
+undef_or_null (obj)
+{
+ return (obj === null || typeof obj === "undefined");
+}
+
+/** Return a relative URL corresponding to HREF, which refers to an anchor
+ of 'config.INDEX_NAME'. HREF must be a USVString representing an
+ absolute or relative URL. For example "foo/bar.html" will return
+ "config.INDEX_NAME#bar".
+
+ @arg {string} href.*/
+function
+with_sidebar_query (href)
+{
+ if (basename (href) === config.INDEX_NAME)
+ return config.INDEX_NAME;
+ else
+ {
+ /* XXX: Do not use the URL API for IE portability. */
+ var url = with_sidebar_query.url;
+ url.setAttribute ("href", href);
+ var new_hash = "#" + basename (url.pathname, /[.]x?html/);
+ /* XXX: 'new_hash !== url.hash' is a workaround to work with links
+ produced by makeinfo which link to an anchor element in a page
+ instead of directly to the page. */
+ if (url.hash && new_hash !== url.hash)
+ new_hash += ("." + url.hash.slice (1));
+ return config.INDEX_NAME + new_hash;
+ }
+}
+
+/* Use the same DOM element for every function call. */
+with_sidebar_query.url = document.createElement ("a");
+
+/** Modify LINKS to handle the iframe based navigation properly. Relative
+ links will be opened inside the corresponding iframe and absolute links
+ will be opened in a new tab. If ID is true then define an "id"
+ attribute with a linkid value for relative links.
+
+ @typedef {HTMLAnchorElement|HTMLAreaElement} Links
+ @arg {Links[]|HTMLCollectionOf<Links>} links
+ @arg {boolean} [id]
+ @return void */
+function
+fix_links (links, id)
+{
+ for (var i = 0; i < links.length; i += 1)
+ {
+ var link = links[i];
+ var href = link_href(link);
+ if (!href)
+ continue;
+ else if (external_manual_url_p (href))
+ link.setAttribute ("target", "_top");
+ else if (! maybe_pageref_url_p (href))
+ link.setAttribute ("target", "_blank");
+ else
+ {
+ var href$ = with_sidebar_query (href);
+ if (href !== href$)
+ {
+ let protocol = location.protocol;
+ if (protocol == "https:" || protocol == "http:")
+ link._href = href$;
+ else
+ link.setAttribute ("href", href$);
+ }
+ if (id)
+ {
+ var linkid = (href$ === config.INDEX_NAME) ?
+ config.INDEX_ID : href_hash (href$);
+ link.setAttribute ("id", linkid);
+ }
+ }
+ }
+}
+
+/** Convert HASH which is something that can be found 'Location.hash' to a
+ "linkid" which can be handled in our model.
+ @arg {string} hash
+ @return {string} linkid */
+function
+normalize_hash (hash)
+{
+ var text = hash.slice (1);
+ /* Some anchor elements are present in 'config.INDEX_NAME' and we need to
+ handle link to them specially (i.e. not try to find their corresponding
+ iframe).*/
+ if (config.MAIN_ANCHORS.includes (text))
+ return config.INDEX_ID + "." + text;
+ else
+ return text;
+}
+
+/** Return an object composed of the filename and the anchor of LINKID.
+ LINKID can have the form "foobar.anchor" or just "foobar".
+ @arg {string} linkid
+ @return {{pageid: string, hash: string}} */
+function
+linkid_split (linkid)
+{
+ if (!linkid.includes ("."))
+ return { pageid: linkid, hash: "" };
+ else
+ {
+ var ref = linkid.match (/^(.+)\.(.*)$/).slice (1);
+ return { pageid: ref[0], hash: "#" + ref[1] };
+ }
+}
+
+/** Convert LINKID which has the form "foobar.anchor" or just "foobar", to
+ an URL of the form `foobar${config.EXT}#anchor`.
+ @arg {string} linkid */
+function
+linkid_to_url (linkid)
+{
+ if (linkid === config.INDEX_ID)
+ return config.INDEX_NAME;
+ else
+ {
+ var link = linkid_split (linkid);
+ return link.pageid + config.EXT + link.hash;
+ }
+}
+
+/** Check if 'URL' may be a cross-reference to another page in this manual. */
+function
+maybe_pageref_url_p (url)
+{
+ return url.match(config.LOCAL_HTML_PAGE_PATTERN);
+}
+
+/** Check if 'URL' is a link to another manual. For locally installed
+ manuals only. */
+function
+external_manual_url_p (url)
+{
+ if (typeof url !== "string")
+ throw new TypeError ("'" + url + "' is not a string");
+
+ return url.match(/^..\//);
+}
+
+/** Return PATHNAME with any leading directory components removed. If
+ specified, also remove a trailing SUFFIX. */
+function
+basename (pathname, suffix)
+{
+ var res = pathname.replace (/.*[/]/, "");
+ if (!suffix)
+ return res;
+ else if (suffix instanceof RegExp)
+ return res.replace (suffix, "");
+ else /* typeof SUFFIX === "string" */
+ return res.replace (new RegExp ("[.]" + suffix), "");
+}
+
+/** Strip last component from PATHNAME and keep the trailing slash. For
+ example if PATHNAME is "/foo/bar/baz.html" then return "/foo/bar/".
+ @arg {string} pathname */
+function
+dirname (pathname)
+{
+ var res = pathname.match (/\/?.*\//);
+ if (res)
+ return res[0];
+ else
+ throw new Error ("'location.pathname' is not recognized");
+}
+
+/** Apply FUNC to each nodes in the NODE subtree. The walk follows a depth
+ first algorithm. Only consider the nodes of type NODE_TYPE. */
+function
+depth_first_walk (node, func, node_type)
+{
+ if (!node_type || (node.nodeType === node_type))
+ func (node);
+
+ for (var child = node.firstChild; child; child = child.nextSibling)
+ depth_first_walk (child, func, node_type);
+}
+
+/** Return the "effective" href attribute of a link (a) element. */
+function
+link_href (alink)
+{
+ return alink._href || alink.getAttribute("href");
+}
+
+/** Return the hash part of HREF without the '#' prefix. HREF must be a
+ string. If there is no hash part in HREF then return the empty
+ string. */
+function
+href_hash (href)
+{
+ if (typeof href !== "string")
+ throw new TypeError (href + " is not a string");
+
+ if (href.includes ("#"))
+ return href.replace (/.*#/, "");
+ else
+ return "";
+}
+
+/** Check if LINKID corresponds to a page containing index links. */
+function
+linkid_contains_index (linkid)
+{
+ return linkid.match (/^.*-index$/i) || linkid.match (/^Index$/);
+}
+
+/** Retrieve PREV, NEXT, and UP links, and local menu from CONTENT and
+ return an object containing references to those links. CONTENT must be
+ an object implementing the ParentNode interface (Element,
+ Document...). */
+function
+navigation_links (content)
+{
+ var links = content.querySelectorAll ("a[accesskey][href]");
+ var res = {};
+ /* links have the form MAIN_FILE.html#FRAME-ID. For convenience
+ only store FRAME-ID. */
+ for (var i = 0; i < links.length; i += 1)
+ {
+ var link = links[i];
+ var nav_id = navigation_links.dict[link.getAttribute ("accesskey")];
+ if (nav_id)
+ {
+ var href = basename (link_href (link));
+ if (href === config.INDEX_NAME)
+ res[nav_id] = config.INDEX_ID;
+ else
+ res[nav_id] = href_hash (href);
+ }
+ else /* this link is part of local table of content. */
+ {
+ res.menu = res.menu || {};
+ res.menu[link.text] = href_hash (link_href (link));
+ }
}
- else if (inside_iframe)
+
+ return res;
+}
+
+navigation_links.dict = { n: "next", p: "prev", u: "up" };
+
+function
+add_icons ()
+{
+ var div = document.createElement ("div");
+ div.setAttribute ("id", "icon-bar");
+ var span = document.createElement ("span");
+ span.innerHTML = "?";
+ // Set tool-tip (on hover)
+ span.setAttribute ("title", "Help for keyboard shortcuts");
+ span.classList.add ("icon");
+ span.addEventListener ("click", function () {
+ store.dispatch (actions.show_help ());
+ }, false);
+ div.appendChild (span);
+ document.body.insertBefore (div, document.body.firstChild);
+}
+
+/** Check if ELEM matches SEARCH
+ @arg {Element} elem
+ @arg {RegExp} rgxp
+ @return {boolean} */
+function
+search (elem, rgxp)
+{
+ /** @type {Text} */
+ var text = null;
+
+ /** @arg {Text} node */
+ function
+ find (node)
+ {
+ if (rgxp.test (node.textContent))
+ {
+ /* Ignore previous match. */
+ var prev = node.parentElement.matches ("span.highlight");
+ text = (prev) ? null : (text || node);
+ }
+ }
+
+ depth_first_walk (elem, find, Node.TEXT_NODE);
+ remove_highlight (elem);
+ if (!text)
+ return false;
+ else
{
- store = new Remote_store ();
- var iframe = init_iframe ();
- window.addEventListener ("DOMContentLoaded", iframe.on_load, false);
- window.addEventListener ("message", iframe.on_message, false);
+ highlight_text (rgxp, text);
+ return true;
}
+}
+
+/** Find the pageid corresponding to forward direction.
+ @arg {any} state
+ @arg {string} linkid
+ @return {string} the forward pageid */
+function
+forward_pageid (state, linkid)
+{
+ var data = state.loaded_nodes[linkid];
+ if (!data)
+ throw new Error ("page not loaded: " + linkid);
+ else if (!data.forward)
+ return null;
else
{
- /* Jump to 'config.INDEX_NAME' and adapt the selected iframe. */
- window.location.replace (with_sidebar_query (window.location.href));
+ var cur = linkid_split (linkid);
+ var fwd = linkid_split (data.forward);
+ if (!fwd.hash && fwd.pageid !== cur.pageid)
+ return fwd.pageid;
+ else
+ return forward_pageid (state, data.forward);
+ }
+}
+
+/** Highlight text in NODE which match RGXP.
+ @arg {RegExp} rgxp
+ @arg {Text} node */
+function
+highlight_text (rgxp, node)
+{
+ /* Skip elements corresponding to highlighted words to avoid infinite
+ recursion. */
+ if (node.parentElement.matches ("span.highlight"))
+ return;
+
+ var matches = rgxp.exec (node.textContent);
+ if (matches)
+ {
+ /* Create an highlighted element containing first match. */
+ var span = document.createElement ("span");
+ span.appendChild (document.createTextNode (matches[0]));
+ span.setAttribute ("id", "search-result");
+ span.classList.add ("highlight");
+
+ var right_node = node.splitText (matches.index);
+ /* Remove first match from right node. */
+ right_node.textContent = right_node.textContent
+ .slice (matches[0].length);
+ node.parentElement.insertBefore (span, right_node);
+ }
+}
+
+/** Remove every highlighted elements and inline their text content.
+ @arg {Element} elem */
+function
+remove_highlight (elem)
+{
+ var spans = elem.getElementsByClassName ("highlight");
+ /* Replace spans with their inner text node. */
+ while (spans.length > 0)
+ {
+ var span = spans[0];
+ var parent = span.parentElement;
+ parent.replaceChild (span.firstChild, span);
}
+}
+
+/*--------------.
+| Entry point. |
+`--------------*/
+
+/** Depending on the role of the document launching this script, different
+ event handlers are registered. This script can be used in the context of:
+
+ - the index page of the manual which manages the state of the application
+ - the iframe which contains the lateral table of content
+ - other iframes which contain other pages of the manual
+
+ This is done to allow referencing the same script inside every HTML page.
+ This has the benefits of reducing the number of HTTP requests required to
+ fetch the Javascript code and simplifying the work of the Texinfo HTML
+ converter. */
+
+/* Display MSG bail out message portably. */
+function
+error (msg)
+{
+ /* XXX: This code needs to be highly portable.
+ Check <https://quirksmode.org/dom/core/> for details. */
+ return function () {
+ var div = document.createElement ("div");
+ div.setAttribute ("class", "error");
+ div.innerHTML = msg;
+ var elem = document.body.firstChild;
+ document.body.insertBefore (div, elem);
+ window.setTimeout (function () {
+ document.body.removeChild (div);
+ }, config.WARNING_TIMEOUT);
+ };
+}
+
+/* Check if current browser supports the minimum requirements required for
+ properly using this script, otherwise bails out. */
+if (features && !(features.es5
+ && features.classlist
+ && features.eventlistener
+ && features.hidden
+ && features.history
+ && features.postmessage
+ && features.queryselector))
+ {
+ window.onload = error ("'info.js' is not compatible with this browser");
+ return;
+ }
+
+/* Until we have a responsive design implemented, fallback to basic
+ HTML navigation for small screen. */
+ /*
+if (window.screen.availWidth < config.SCREEN_MIN_WIDTH)
+ {
+ window.onload =
+ error ("screen width is too small to display the table of content");
+ return;
+ }
+*/
+
+register_polyfills ();
+/* Let the config provided by the user mask the default one. */
+config = Object.assign (config, user_config);
+
+var inside_iframe = top !== window;
+var inside_index_page = window.location.pathname === config.INDEX_NAME
+ || window.location.pathname.endsWith ("/" + config.INDEX_NAME)
+ || window.location.pathname.endsWith ("/");
+
+if (inside_index_page)
+ {
+ var initial_state = {
+ /* Dictionary associating page ids to next, prev, up, forward,
+ backward link ids. */
+ loaded_nodes: {},
+ /* Dictionary associating keyword to linkids. */
+ index: undefined,
+ /* page id of the current page. */
+ current: config.INDEX_ID,
+ /* dictionary associating a page id to a boolean. */
+ ready: {},
+ /* Current mode for handling history. */
+ history: "replaceState",
+ /* Define the name of current text input. */
+ text_input: null
+ };
+
+ store = new Store (updater, initial_state);
+ var index = init_index_page ();
+ var sidebar = init_sidebar ();
+ window.addEventListener ("DOMContentLoaded", function () {
+ index.on_load ();
+ sidebar.on_load ();
+ }, false);
+ window.addEventListener ("message", index.on_message, false);
+ window.addEventListener ("message", sidebar.on_message, false);
+ window.onpopstate = index.on_popstate;
+ }
+else if (inside_iframe)
+ {
+ store = new Remote_store ();
+ var iframe = init_iframe ();
+ window.addEventListener ("DOMContentLoaded", iframe.on_load, false);
+ window.addEventListener ("message", iframe.on_message, false);
+ }
+else
+ {
+ /* Jump to 'config.INDEX_NAME' and adapt the selected iframe. */
+ window.location.replace (with_sidebar_query (window.location.href));
+ }
- /* Register common event handlers. */
- window.addEventListener ("beforeunload", on_unload, false);
- window.addEventListener ("click", on_click, false);
- /* XXX: handle 'keydown' event instead of 'keypress' since Chromium
- doesn't handle the 'Escape' key properly. See
- https://bugs.chromium.org/p/chromium/issues/detail?id=9061. */
- window.addEventListener ("keydown", on_keydown, false);
+/* Register common event handlers. */
+window.addEventListener ("beforeunload", on_unload, false);
+window.addEventListener ("click", on_click, false);
+/* XXX: handle 'keydown' event instead of 'keypress' since Chromium
+ doesn't handle the 'Escape' key properly. See
+ https://bugs.chromium.org/p/chromium/issues/detail?id=9061. */
+window.addEventListener ("keydown", on_keydown, false);
} (window["Modernizr"], window["INFO_CONFIG"]));
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- branch master updated: * js/info.js: Exdent entire body of file (except inside a string constant) in attempt to make nested functions more apparent.,
Gavin D. Smith <=