diff options
author | Holger Voormann | 2021-02-10 11:18:03 +0000 |
---|---|---|
committer | Holger Voormann | 2021-03-21 17:42:44 +0000 |
commit | e9c0486a398a955c204a17274f5f90e021fd33a4 (patch) | |
tree | b55dab686266e5a885fb76e2b697c27f1b2a2784 | |
parent | 254ebab55170f7d237d8498eccc72c3be873c7c4 (diff) | |
download | eclipse.platform.ua-e9c0486a398a955c204a17274f5f90e021fd33a4.tar.gz eclipse.platform.ua-e9c0486a398a955c204a17274f5f90e021fd33a4.tar.xz eclipse.platform.ua-e9c0486a398a955c204a17274f5f90e021fd33a4.zip |
Bug 571831 - Externalize Strings of modernized help UI
Change-Id: I8619c56d48b4f5134863b4f471bd91cfcce08116
Signed-off-by: Holger Voormann <eclipse@voormann.de>
8 files changed, 276 insertions, 82 deletions
diff --git a/org.eclipse.help.webapp/index.jsp b/org.eclipse.help.webapp/index.jsp index 33c919254..813ac584f 100644 --- a/org.eclipse.help.webapp/index.jsp +++ b/org.eclipse.help.webapp/index.jsp @@ -12,6 +12,7 @@ IBM Corporation - initial API and implementation --%> <%@ page import="org.eclipse.help.internal.webapp.data.*" errorPage="/advanced/err.jsp" contentType="text/html; charset=UTF-8"%> +<%@ page import="org.eclipse.help.internal.webapp.HelpUi" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.net.URL" %> <%@ page import="java.util.UUID" %> @@ -68,7 +69,7 @@ // Read it as InputStream and convert it to a String // (by using a Scanner with a delimiter that cannot be found: \A - start of input) Scanner scanAll = new Scanner(resourceAsUrl.openStream()).useDelimiter("\\A"); - response.getWriter().write(scanAll.hasNext() ? scanAll.next() : ""); + response.getWriter().write(HelpUi.resolve(scanAll.hasNext() ? scanAll.next() : "", request)); } catch (Exception e) { // Experimental UI resource not found, so fall back to legacy UI request.getRequestDispatcher("/advanced/index.jsp" + data.getQuery()).forward(request, response); diff --git a/org.eclipse.help.webapp/m/README.md b/org.eclipse.help.webapp/m/README.md index 30e66bfa8..dc2ee21d0 100644 --- a/org.eclipse.help.webapp/m/README.md +++ b/org.eclipse.help.webapp/m/README.md @@ -45,6 +45,13 @@ For example, when the plugin has the symbolic name `com.example.my_plugin` with -Dorg.eclipse.help.webapp.experimental.ui=com.example.my_plugin/customized-help/index.html +The `m/index.js` JavaScript file can be customized in the same way: + + -Dorg.eclipse.help.webapp.experimental.ui.js=<plugin>/<optional-path>/<file> + +The customized HTML and JavaScript file can contain placeholders (see +[`org.eclipse.help.internal.webapp.HelpUi.resolve(String, HttpServletRequest)`](../src/org/eclipse/help/internal/webapp/HelpUi.java)). + ## Further development @@ -69,7 +76,6 @@ buttons and bookmark support) and to determine the initial content page. * [`/advanced/bookmarksView.jsp`](127.0.0.1:49999/help/advanced/print.jsp?topic=/../nav/0) * Storing UI settings: TOC side bar width and show/hidden, search results filter tree expanded or collapsed, etc. * Things to improve (where this prototype currently falls behind the legacy UI): - * Internationalization (by externalize Strings) * Right-to-left (RTL) support * Accessibility (HTML5 ARIA, keyboard support, color contrast, etc.) * Dark theme support diff --git a/org.eclipse.help.webapp/m/index.html b/org.eclipse.help.webapp/m/index.html index 69c78558e..abce8d701 100644 --- a/org.eclipse.help.webapp/m/index.html +++ b/org.eclipse.help.webapp/m/index.html @@ -4,7 +4,7 @@ <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Help</title> + <title><%html:Help%></title> <link rel="stylesheet" type="text/css" href="m/index.css"> <script type="text/javascript" src="m/index.js"></script> </head> diff --git a/org.eclipse.help.webapp/m/index.js b/org.eclipse.help.webapp/m/index.js index 6221fe967..3edb9034f 100644 --- a/org.eclipse.help.webapp/m/index.js +++ b/org.eclipse.help.webapp/m/index.js @@ -8,46 +8,29 @@ * SPDX-License-Identifier: EPL-2.0 ******************************************************************************/ (function(window, document) { - var SMALL_SCREEN_WIDTH = 768; var LOGO_ICON_WIDTH = 36; var LOGO_FULL_WIDTH = 146; var MENU_FONT_SIZING = 0; - var MENU_HELP = 0 // 0 or e.g. 'topic/org.eclipse.help.base/doc/help_home.html' - var MENU_HELP_LABEL = 'Help'; - var MENU_HELP_DESCRIPTION = 'Using the help system' + var MENU_HELP = 0; // 0 or e.g. 'topic/org.eclipse.help.base/doc/help_home.html' + var MENU_HELP_LABEL = '<%js:menu_item_help_label%>'; + var MENU_HELP_DESCRIPTION = '<%js:menu_item_help_description%>'; var MENU_ABOUT = 0; var TOC_SIDEBAR_DEFAULT_WIDTH = 380; var TOC_SIDEBAR_MINIMUM_WIDTH = 76; var TOC_SIDEBAR_WIDTH_COOKIE_NAME = 'toc_width'; - var TOC_ICON_DESCRIPTION = 'Toggle table of content'; var TOC_ICON = '<svg width="20" height="20" viewBox="0 0 20 20"><path fill="currentColor" d="M19 5H1V3h18v2zm0 10H1v2h18v-2zm-4-6H1v2h14V9z"/></svg>'; - var HISTORY_BACK_DESCRIPTION = 'Go back one page'; var HISTORY_BACK_ICON = '<svg width="20" height="20" viewBox="0 0 20 20"><path fill="currentColor" d="m 8.27224,17.95644 c 0.39048,0.390343 1.023592,0.390211 1.413938,0 0.390613,-0.390612 0.390613,-1.023597 -1.33e-4,-1.41421 L 4.144057,11.000499 18.27041,10.999748 c 0.55219,-1.31e-4 0.999731,-0.447671 0.999731,-1.0001299 -1.34e-4,-0.552189 -0.447674,-0.999595 -0.999864,-0.999595 l -14.126755,9.99e-4 5.542723,-5.543204 c 0.390479,-0.390479 0.390479,-1.023727 0,-1.414074 -0.195307,-0.195173 -0.451138,-0.292892 -0.707102,-0.292892 -0.255832,0 -0.511664,0.09772 -0.70697,0.292759 l -7.249421,7.250043 c -0.187575,0.18744 -0.292893,0.441666 -0.292893,0.7069649 1.33e-4,0.2653 0.105451,0.519396 0.293025,0.707237 z"/></svg>'; - var HISTORY_FORWARD_DESCRIPTION = 'Go back one page'; var HISTORY_FORWARD_ICON = '<svg width="20" height="20" viewBox="0 0 20 20"><path fill="currentColor" d="m 11.72776,17.95644 c -0.39048,0.390343 -1.023592,0.390211 -1.413938,0 -0.390613,-0.390612 -0.390613,-1.023597 1.33e-4,-1.41421 L 15.855943,11.000499 1.72959,10.999748 C 1.1774,10.999617 0.729859,10.552077 0.729859,9.9996181 0.729993,9.4474291 1.177533,9.0000231 1.729723,9.0000231 l 14.126755,9.99e-4 -5.542723,-5.543204 c -0.390479,-0.390479 -0.390479,-1.023727 0,-1.414074 0.195307,-0.195173 0.451138,-0.292892 0.707102,-0.292892 0.255832,0 0.511664,0.09772 0.70697,0.292759 l 7.249421,7.250043 c 0.187575,0.18744 0.292893,0.441666 0.292893,0.7069649 -1.33e-4,0.2653 -0.105451,0.519396 -0.293025,0.707237 z"/></svg>'; var BOOKMARKS_ICON = '<svg width="20" height="20" viewBox="0 0 20 20"><path fill="currentColor" d="M 10.019531 0.5 A 1.000065 1.000065 0 0 0 9.1035156 1.0566406 L 6.5742188 6.1816406 L 0.91796875 7.0039062 A 1.000065 1.000065 0 0 0 0.36523438 8.7089844 L 4.4570312 12.699219 L 3.4902344 18.332031 A 1.000065 1.000065 0 0 0 4.9414062 19.384766 L 10 16.726562 L 15.058594 19.384766 A 1.000065 1.000065 0 0 0 16.509766 18.332031 L 15.542969 12.699219 L 19.634766 8.7089844 A 1.000065 1.000065 0 0 0 19.082031 7.0039062 L 13.425781 6.1816406 L 10.896484 1.0566406 A 1.000065 1.000065 0 0 0 10.019531 0.5 z M 10 3.7597656 L 11.865234 7.5390625 A 1.000065 1.000065 0 0 0 12.617188 8.0859375 L 16.789062 8.6914062 L 13.771484 11.632812 A 1.000065 1.000065 0 0 0 13.482422 12.517578 L 14.195312 16.673828 L 10.464844 14.710938 A 1.000065 1.000065 0 0 0 9.5351562 14.710938 L 5.8046875 16.673828 L 6.5175781 12.517578 A 1.000065 1.000065 0 0 0 6.2285156 11.632812 L 3.2109375 8.6914062 L 7.3828125 8.0859375 A 1.000065 1.000065 0 0 0 8.1347656 7.5390625 L 10 3.7597656 z"/></svg>'; - var BOOKMARKS_DESCRIPTION = 'Bookmarks'; - var BOOKMARKS_CLOSE_DESCRIPTION = 'Close bookmarks'; - var BOOKMARKS_ADD_PAGE_DESCRIPTION = 'Bookmark current page'; - var BOOKMARKS_ADD_SEARCH_DESCRIPTION = 'Bookmark current search'; - var BOOKMARKS_DELETE = 'Delete'; - var BOOKMARKS_DELETE_DESCRIPTION = 'Delete this bookmarks (cannot be undone)'; - var BOOKMARKS_DELETE_ALL = 'Delete all bookmarks'; - var BOOKMARKS_DELETE_ALL_DESCRIPTION = 'Delete all bookmarks (cannot be undone)'; var BOOKMARKS_PATTERN = new RegExp('<tr[^<]*<td[^<]*<a\\s+(?:(?!href)[\\w\\-]+\\s*=\\s*(?:(?:\'[^\']*\')|(?:"[^"]*"))\\s+)*href\\s*=\\s*\'([^\']*)\'[^<]*<img[^>]*>\\s*([^<]*)</a>', 'gi'); var MENU_ICON = '<svg width="20" height="20" viewBox="0 0 20 20"><path fill="currentColor" d="M 10 1.5 A 2 2 0 0 0 8 3.5 A 2 2 0 0 0 10 5.5 A 2 2 0 0 0 12 3.5 A 2 2 0 0 0 10 1.5 z M 10 8 A 2 2 0 0 0 8 10 A 2 2 0 0 0 10 12 A 2 2 0 0 0 12 10 A 2 2 0 0 0 10 8 z M 10 14.5 A 2 2 0 0 0 8 16.5 A 2 2 0 0 0 10 18.5 A 2 2 0 0 0 12 16.5 A 2 2 0 0 0 10 14.5 z"/></svg>'; - var MENU_ICON_DESCRIPTION = 'Show menu'; var MENU_CLOSE_ICON = '<svg width="20" height="20" viewBox="0 0 20 20"><path fill="currentColor" d="M 4.34375 2.9296875 L 2.9296875 4.34375 L 8.5859375 10 L 2.9296875 15.65625 L 4.34375 17.070312 L 10 11.414062 L 15.65625 17.070312 L 17.070312 15.65625 L 11.414062 10 L 17.070312 4.34375 L 15.65625 2.9296875 L 10 8.5859375 L 4.34375 2.9296875 z"/></svg>'; - var MENU_CLOSE_ICON_DESCRIPTION = 'Hide menu'; var TREE_HANDLE = '<svg width="24" height="24" viewBox="0 0 24 24" focusable="false" role="presentation">-<path d="M10.294 9.698a.988.988 0 0 1 0-1.407 1.01 1.01 0 0 1 1.419 0l2.965 2.94a1.09 1.09 0 0 1 0 1.548l-2.955 2.93a1.01 1.01 0 0 1-1.42 0 .988.988 0 0 1 0-1.407l2.318-2.297-2.327-2.307z" fill="currentColor"/></svg>'; var BOOK_NAME_SHORTENER = function shortenBookName(bookName) { return bookName.replace(/\s+(Documentation\s*)?(\-\s+([0-9,\-]+\s+)?Preview(\s+[0-9,\-]+)?\s*)?$/i, ''); }; var BOOK_SCOPE_BY_DEFAULT = 0; var BOOK_SCOPE_COOKIE = 'book-scope'; - var SEARCH_SCOPE_CLOSE_DESCRIPTION = 'Close scopes'; var SEARCH_ICON = '<svg width="20" height="20" viewBox="0 0 20 20"><g fill="#fff"><path fill="currentColor" d="M 7.5 0 C 3.3578644 0 0 3.3578644 0 7.5 C 0 11.642136 3.3578644 15 7.5 15 C 8.8853834 14.997 10.242857 14.610283 11.421875 13.882812 L 17.185547 19.662109 C 17.632478 20.113489 18.36112 20.112183 18.8125 19.660156 L 19.623047 18.845703 C 20.072507 18.398153 20.072507 17.665594 19.623047 17.214844 L 13.871094 11.447266 C 14.607206 10.26212 14.998156 8.8951443 15 7.5 C 15 3.3578644 11.642136 0 7.5 0 z M 7.5 2 A 5.5 5.5 0 0 1 13 7.5 A 5.5 5.5 0 0 1 7.5 13 A 5.5 5.5 0 0 1 2 7.5 A 5.5 5.5 0 0 1 7.5 2 z"/></g></svg>'; - var SEARCH_FIELD_DESCRIPTION = '* = any string\n? = any character\n"" = phrase\nAND, OR & NOT = boolean operators'; - var SEARCH_FIELD_PLACEHOLDER = 'Search'; var BASE_URL; var SEARCH_BASE_URL; var SEARCH_HITS_MAX = 500; @@ -55,10 +38,6 @@ var SEARCH_RESULTS_INDEXING_PATTERN = new RegExp('[\'"]divProgress[\'"]\\s+STYLE\\s*=\\s*[\'"]\\s*width\\s*:\\s*([\\d]+)\\s*px', 'i'); var SEARCH_RESULTS_PATTERN = new RegExp('<tr[^<]*<td[^<]*<img[^<]*</td[^<]*<td[^<]*<a\\s+(?:(?!href)(?!title)[\\w\\-]+\\s*=\\s*(?:(?:\'[^\']*\')|(?:"[^"]*"))\\s+)*(href|title)\\s*=\\s*"([^"]*)"\\s+(?:(?!href)(?!title)[\\w\\-]+\\s*=\\s*(?:(?:\'[^\']*\')|(?:"[^"]*"))\\s+)*(href|title)\\s*=\\s*"([^"]*)"[^>]*>([^<]*)</a>(?:(?:(?!<[/]?tr)[\\s\\S])*</tr\\s*>\\s*<tr(?:(?!</tr)(?!class="location">)[\\s\\S])*class="location">((?:(?!</div)[\\s\\S])*))?(?:(?:(?!</tr)(?!\\sclass=["\']description["\'])[\\s\\S])*</tr){1,2}(?:(?!</tr)(?!\\sclass=["\']description["\'])[\\s\\S])*\\sclass=["\']description["\'][^>]*>([^<]*)', 'gi'); var SEARCH_RESULTS_BREADCRUMB_SNIPPET_PATTERN = new RegExp('<a\\s+href="([^"]+)">([^<]+)</a>', 'gi'); - var SEARCH_SCOPE_LABEL_NONE = 'All'; - var SEARCH_SCOPE_LABEL_BOOK = 'Book'; - var SEARCH_SCOPE_LABEL_CHAPTER = 'Chapter'; -// var SEARCH_SCOPE_LABEL_TOPIC = 'Find in page'; var SEARCH_SCOPE_CURRENT_PATTERN = new RegExp('<div\\s+id\\s*=\\s*"scope"\\s*>([^<]*)<'); var SEARCH_SCOPE_ALL_PATTERN = new RegExp('<a\\s+(?:(?!title)[\\w\\-]+\\s*=\\s*(?:(?:\'[^\']*\')|(?:"[^"]*"))\\s+)*title\\s*=\\s*"([^"]*)"', 'gi'); var SEARCH_SCOPE_NAME_PATTERN = new RegExp('<input\\s+type\\s*=\\s*["\']text["\']\\s+(?:(?!value)[\\w\\-]+\\s*=\\s*(?:(?:\'[^\']*\')|(?:"[^"]*"))\\s+)*value\\s*=\\s*\'([^\']*)\'', 'i'); @@ -72,7 +51,7 @@ // TODO integration: the browser should not have to calculate the following variables itself; // only "init()" should be called instead var embeddedMode = 1; - var title = 'Help'; + var title = '<%js:Help%>'; var bookmarksPage; var scopesPage; var searchPage; @@ -167,13 +146,13 @@ if (title == null || title == '') { title = url; } - createElement(bookmarksPage, 'span', 'g', 'Add:'); + createElement(bookmarksPage, 'span', 'g', '<%js:bookmarks_add_field_label%>'); var addArea = createElement(bookmarksPage, 'span', 'a'); var input = createElement(addArea, 'input'); input.type = 'text'; input.autocomplete = 'off'; input.value = title; - var addButtonTooltip = searchPage.o ? BOOKMARKS_ADD_SEARCH_DESCRIPTION : BOOKMARKS_ADD_PAGE_DESCRIPTION; + var addButtonTooltip = searchPage.o ? '<%js:bookmarks_add_button_description_search%>' : '<%js:bookmarks_add_button_description_page%>'; createButton(addArea, BOOKMARKS_ICON, addButtonTooltip, function() { try { var url = frames.c.location.href; @@ -201,7 +180,7 @@ } catch (e) {} // "x" button - setClassName(createButton(bookmarksPage, MENU_CLOSE_ICON, BOOKMARKS_CLOSE_DESCRIPTION, function() { + setClassName(createButton(bookmarksPage, MENU_CLOSE_ICON, '<%js:bookmarks_button_description_close%>', function() { bookmarksPage.s(); }), 'b bc'); @@ -212,7 +191,7 @@ BOOKMARKS_PATTERN.lastIndex = 0; for (var match; (match = BOOKMARKS_PATTERN.exec(responseText)) != null;) { if (!ol) { - createElement(bookmarksPage, 0, 'g', 'Bookmarks:'); + createElement(bookmarksPage, 0, 'g', '<%js:bookmarks_heading%>'); ol = createElement(bookmarksPage, 'ol'); } var groups = []; @@ -223,8 +202,8 @@ var li = createElement(ol, 'li'); var href = groups[0].substring(0, 3) == '../' ? BASE_URL + '/' + groups[0] : groups[0]; var deleteButton = createButton(li, - BOOKMARKS_DELETE, - BOOKMARKS_DELETE_DESCRIPTION, + '<%js:bookmarks_delete_button_label%>', + '<%js:bookmarks_delete_button_description%>', function(href, title, li) { return function() { remoteRequest( BASE_URL + 'advanced/bookmarksView.jsp?operation=remove&' @@ -244,10 +223,10 @@ createElement(a, 'span', 0, groups[1]); } if (deleteButtons.length) { - var editButton = createButton(bookmarksPage, 'Edit', 'Delete bookmarks', function() { - var editMode = editButton.innerHTML == 'Edit'; - editButton.innerHTML = editMode ? BOOKMARKS_DELETE_ALL : 'Edit'; - editButton.title = editMode ? BOOKMARKS_DELETE_ALL_DESCRIPTION : 'Delete bookmarks'; + var editButton = createButton(bookmarksPage, '<%js:bookmarks_edit_button_label%>', '<%js:bookmarks_edit_button_description%>', function() { + var editMode = editButton.innerHTML == decodeHtml('<%js:bookmarks_edit_button_label%>'); + editButton.innerHTML = decodeHtml(editMode ? '<%js:bookmarks_delete_all_button_label%>' : '<%js:bookmarks_edit_button_label%>'); + editButton.title = editMode ? '<%js:bookmarks_delete_all_button_description%>' : '<%js:bookmarks_edit_button_description%>'; setClassName(editButton, editMode ? 'b br' : 'b'); for (var i = 0; i < deleteButtons.length; i++) { deleteButtons[i].style.display = 'inline-block'; @@ -258,7 +237,7 @@ } }); } - createButton(bookmarksPage, 'Cancel', BOOKMARKS_CLOSE_DESCRIPTION, function() { bookmarksPage.s(); }); + createButton(bookmarksPage, '<%js:bookmarks_button_label_close%>', '<%js:bookmarks_button_description_close%>', function() { bookmarksPage.s(); }); }); } @@ -278,7 +257,7 @@ scopesPage.innerHTML = ''; // "x" button - setClassName(createButton(scopesPage, MENU_CLOSE_ICON, SEARCH_SCOPE_CLOSE_DESCRIPTION, function() { + setClassName(createButton(scopesPage, MENU_CLOSE_ICON, '<%js:scopes_close_button_description%>', function() { scopesPage.s(); }), 'b bc'); @@ -290,7 +269,7 @@ function(responseText) { var match = SEARCH_SCOPE_NAME_PATTERN.exec(responseText); if (match) { - createElement(scopesPage, 'span', 'g', 'Scope:'); + createElement(scopesPage, 'span', 'g', '<%js:scopes_scope_field_label%>'); var scopeName = decodeHtml(match[1]); var nameInput = createElement(createElement(scopesPage, 'span', 'a'), 'input'); nameInput.type = 'text'; @@ -357,17 +336,17 @@ return hrefs; } if (SEARCH_SCOPE_IS_NEW_PATTERN.exec(responseText)) { - createButton(scopesPage, 'Create', 'Add new scope', function() { + createButton(scopesPage, '<%js:scopes_scope_create_button_label%>', '<%js:scopes_scope_create_button_description%>', function() { doScopesOperation( 'add&oldName=&workingSet=' + encodeURIComponent(nameInput.value) + getHrefs()); }); } else { - var deleteButton = createButton(scopesPage, 'Delete', 'Delete this scope', function() { + var deleteButton = createButton(scopesPage, '<%js:scopes_scope_delete_button_label%>', '<%js:scopes_scope_delete_button_description%>', function() { doScopesOperation('remove&workingSet=' + encodeURIComponent(scopeName)); }); deleteButton.className = 'b br'; - createButton(scopesPage, 'Apply', 'Update this scope', function() { + createButton(scopesPage, '<%js:scopes_scope_apply_button_label%>', '<%js:scopes_scope_apply_button_description%>', function() { doScopesOperation( 'edit&oldName=' + encodeURIComponent(scopeName) + '&workingSet=' @@ -376,10 +355,10 @@ setSearchScope([4, scopeName, scopeNr]); }); } - createButton(scopesPage, 'Cancel', 'Go back to list of scopes',function() { showScopesPage(); }); + createButton(scopesPage, '<%js:scopes_scope_cancel_button_label%>', '<%js:scopes_scope_cancel_button_description%>', function() { showScopesPage(); }); } else { - createElement(scopesPage, 0, 'g', 'Scopes:'); + createElement(scopesPage, 0, 'g', '<%js:scopes_heading%>'); var ol = createElement(scopesPage, 'ol'); SEARCH_SCOPE_ALL_PATTERN.lastIndex = 0; for (var scopeIndex = 0; (match = SEARCH_SCOPE_ALL_PATTERN.exec(responseText)) != null;) { @@ -391,8 +370,8 @@ }(scopeName, scopeIndex), 'ba'); scopeIndex++; } - createButton(scopesPage, 'New', 'Add a new scope', function() { showScopesPage('operation=add'); }); - createButton(scopesPage, 'Cancel', SEARCH_SCOPE_CLOSE_DESCRIPTION, function() { scopesPage.s(); }); + createButton(scopesPage, '<%js:scopes_new_button_label%>', '<%js:scopes_new_button_description%>', function() { showScopesPage('operation=add'); }); + createButton(scopesPage, '<%js:scopes_close_button_label%>', '<%js:scopes_close_button_description%>', function() { scopesPage.s(); }); } }); } @@ -439,7 +418,7 @@ scopesPage.s(); // search page - searchPage = createElement(getElementById('m'), 0, 'c', 'Loading...'); + searchPage = createElement(getElementById('m'), 0, 'c', '<%js:search_page_loading%>'); searchPage.id = 'r'; searchPage.s = function(show) { searchPage.o = !!show; @@ -464,12 +443,12 @@ var header = getElementById('h'); var toolbarContainer = createElement(header); var toolbar = createElement(toolbarContainer, 0, 'y'); - var tocSidebarToggleButton = createButton(toolbar, TOC_ICON, TOC_ICON_DESCRIPTION); + var tocSidebarToggleButton = createButton(toolbar, TOC_ICON, '<%js:toc_toggle_button_description%>'); if (embeddedMode) { - createButton(toolbar, HISTORY_BACK_ICON, HISTORY_BACK_DESCRIPTION, function() { + createButton(toolbar, HISTORY_BACK_ICON, '<%js:back_button_description%>', function() { window.history.back(); }); - createButton(toolbar, HISTORY_FORWARD_ICON, HISTORY_FORWARD_DESCRIPTION, function() { + createButton(toolbar, HISTORY_FORWARD_ICON, '<%js:forward_button_description%>', function() { window.history.forward(); }); } @@ -924,7 +903,7 @@ var searchFieldArea = createElement(createElement(searchFieldAreaWrapper, 0, 'q1'), 'form', 'q'); if (embeddedMode) { - createButton(searchFieldAreaWrapper, BOOKMARKS_ICON, BOOKMARKS_DESCRIPTION, function() { + createButton(searchFieldAreaWrapper, BOOKMARKS_ICON, '<%js:bookmarks_button_description%>', function() { bookmarksPage.s(!bookmarksPage.o); }); } @@ -970,14 +949,12 @@ currentChapter = tocLi.n.t; } } - menuItems.push(createElement(booksDropDownUl, 'li', searchScope.l == 0 ? 'x': 0, SEARCH_SCOPE_LABEL_NONE)); + menuItems.push(createElement(booksDropDownUl, 'li', searchScope.l == 0 ? 'x': 0, '<%js:scope_none_label%>')); dataItems.push([0]); - menuItems.push(createElement(booksDropDownUl, 'li', searchScope.l == 1 ? 'x': 0, SEARCH_SCOPE_LABEL_BOOK + (currentBook ? ': ' + currentBook : ''))); + menuItems.push(createElement(booksDropDownUl, 'li', searchScope.l == 1 ? 'x': 0, '<%js:scope_book_label%>' + (currentBook ? ': ' + currentBook : ''))); dataItems.push([1]); - menuItems.push(createElement(booksDropDownUl, 'li', searchScope.l == 2 ? 'x': 0, SEARCH_SCOPE_LABEL_CHAPTER + (currentChapter ? ': ' + currentChapter : ''))); + menuItems.push(createElement(booksDropDownUl, 'li', searchScope.l == 2 ? 'x': 0, '<%js:scope_chapter_label%>' + (currentChapter ? ': ' + currentChapter : ''))); dataItems.push([2]); -// menuItems.push(createElement(booksDropDownUl, 'li', searchScope.l == 3 ? 'x': 0, SEARCH_SCOPE_LABEL_TOPIC)); -// dataItems.push([3]); remoteRequest(BASE_URL + 'advanced/workingSetManager.jsp?t=' + Date.now(), function(menuItems, dataItems) { return function(responseText) { var delimiter = 'l '; @@ -992,7 +969,7 @@ dataItems.push([4, label, scopeIndex]); scopeIndex++; } - menuItems.push(createElement(booksDropDownUl, 'li', 'l y', 'Scopes...')); + menuItems.push(createElement(booksDropDownUl, 'li', 'l y', '<%js:scope_manage_label%>')); dataItems.push([5]); toMenu(booksButton, menuItems, dataItems, setSearchScope); } @@ -1097,10 +1074,10 @@ var searchField = createElement(wrap, 'input'); searchField.id = 'q'; searchField.type = 'text'; - searchField.alt = SEARCH_FIELD_DESCRIPTION; - searchField.title = SEARCH_FIELD_DESCRIPTION; + searchField.alt = '<%js:search_field_description%>'; + searchField.title = '<%js:search_field_description%>'; searchField.autocomplete = 'off'; - searchField.placeholder = SEARCH_FIELD_PLACEHOLDER; + searchField.placeholder = '<%js:search_field_placeholder%>'; addEvent(searchField, 'input', search); // for IE 8 do also on 'propertychange' addEvent(searchField, 'focus', search); @@ -1216,7 +1193,7 @@ window.frames.c.location = url; return; } - setInnerHtml(searchPage, 'Searching...'); + setInnerHtml(searchPage, '<%js:search_page_loading%>'); searchPage.scrollTop = 0; } else if (query == proposals.q) { preventDefault(e); @@ -1356,7 +1333,7 @@ var match = SEARCH_RESULTS_INDEXING_PATTERN.exec(data); if (match != null) { if (fullSearch) { - setInnerHtml(searchPage, 'Indexing... ' + match[1] + '%'); + setInnerHtml(searchPage, '<%js:search_indexing%>'.replace('{0}', match[1])); } return; } @@ -1454,13 +1431,13 @@ var parentElement = fullSearch ? searchPage : proposals; setInnerHtml(parentElement, ''); parentElement.q = query; - parentElement.l = 'Search' + (searchScope.l > 0 ? ' (' + searchScopeLabel+ ')': '') + ': ' + searchWord; + parentElement.l = (searchScope.l > 0 ? '<%js:search_scoped_title%>' : '<%js:search_title%>').replace('{0}', searchWord).replace('{1}', searchScopeLabel); if (fullSearch) { document.title = searchPage.l + ' - ' + title; // no results? if (!results.length) { - var noResults = createElement(searchPage, 0, 'r0', 'No results found for '); + var noResults = createElement(searchPage, 0, 'r0', '<%js:searchpage_nothing_found_prefix%>' + ' '); createElement(noResults, 'strong', 0, searchWord); return; } @@ -1530,7 +1507,9 @@ var labelText = ''; if (isRoot) { checkbox.style.display = 'none'; - labelText = 'Results ' + (searchScope && searchScope.l > 0 ? 'in ' : ''); + labelText = (searchScope && searchScope.l > 0 + ? '<%js:searchpage_heading_scoped%>' + : '<%js:searchpage_heading%>') + ' '; } else { addEvent(checkbox, 'click', (function(liCheck, li) { return function() { @@ -2100,8 +2079,8 @@ var closeMenuButton = createElement(createElement(menu, 0, 'e'), 'a', 'b'); closeMenuButton.href = '#'; addEvent(closeMenuButton, 'click', menu.o); - closeMenuButton.alt = MENU_CLOSE_ICON_DESCRIPTION; - closeMenuButton.title = MENU_CLOSE_ICON_DESCRIPTION; + closeMenuButton.alt = '<%js:menu_button_close_description%>'; + closeMenuButton.title = '<%js:menu_button_close_description%>'; setInnerHtml(closeMenuButton, MENU_CLOSE_ICON); // "Highlight search terms" dummy @@ -2110,35 +2089,37 @@ // dummy for highlight() in org.eclipse.help.webapp/advanced/highlight.js }; window.ContentToolbarFrame = new HighlightConnector(); - var highlight = createMenuItem(0, 'Toggle search term highlighting', toggleHighlight, 'ah'); - createElement(highlight, 'span', 'hl', 'Highlight'); + var highlight = createMenuItem(0, '<%js:menu_item_highlighting_label_description%>', toggleHighlight, 'ah'); + createElement(highlight, 'span', 'hl', '<%js:menu_item_highlighting_label_prefix%>'); + createElement(highlight, 'span', 'hs', ' '); + createElement(highlight, 'span', 'ht', '<%js:menu_item_highlighting_label_highlighting%>'); createElement(highlight, 'span', 'hs', ' '); - createElement(highlight, 'span', 'ht', 'search term'); + createElement(highlight, 'span', 'hl', '<%js:menu_item_highlighting_label_suffix%>'); toggleHighlight(0, 1); // "Font: - +" if (MENU_FONT_SIZING) { var fontSizer = createElement(menu); fontSizer.id = 'af'; - createElement(fontSizer, 'span', 0, 'Font:'); - createMenuItem('\u2013', 'Decrease font size', function() { setFontSize(0); }, 'afm', 0, fontSizer); - createMenuItem('+', 'Increase font size', function() { setFontSize(1); }, 'afp', 0, fontSizer); + createElement(fontSizer, 'span', 0, '<%js:menu_item_font_label%>'); + createMenuItem('\u2013', '<%js:menu_item_font_minus_button_description%>', function() { setFontSize(0); }, 'afm', 0, fontSizer); + createMenuItem('+', '<%js:menu_item_font_plus_button_description%>', function() { setFontSize(1); }, 'afp', 0, fontSizer); } // "Bookmarks..." if (embeddedMode) { - createMenuItem('Bookmarks...', 'Bookmark current topic and manage existing bookmarks', function() { + createMenuItem('<%js:menu_item_bookmarks_label%>', '<%js:menu_item_bookmarks_description%>', function() { bookmarksPage.s(1); }); } // "Search scopes..." - createMenuItem('Search scopes...', 'Manage search scopes', function() { + createMenuItem('<%js:menu_item_scopes_label%>', '<%js:menu_item_scopes_description%>', function() { scopesPage.s(1); }); // "Print topic..." - createMenuItem('Print topic...', 'Print topic without its subtopics', function() { + createMenuItem('<%js:menu_item_print_topic_label%>', '<%js:menu_item_print_topic_description%>', function() { try { getElementById('c').contentWindow.print(); } catch (e) { @@ -2146,7 +2127,7 @@ }, 'ap'); // "Print chapter..." - createMenuItem('Print chapter...', 'Print topic including subtopics', printChapter, 'app'); + createMenuItem('<%js:menu_item_print_chapter_label%>', '<%js:menu_item_print_chapter_description%>', printChapter, 'app'); // "Help" if (MENU_HELP) { @@ -2155,14 +2136,14 @@ // "About" if (MENU_ABOUT) { - createMenuItem('About', 'Configuration details', 0, 'aa', 'about.html'); + createMenuItem('<%js:menu_item_about_label%>', '<%js:menu_item_about_description%>', 0, 'aa', 'about.html'); } // show menu button var menuButton = createElement(getElementById('h'), 'a', 'b'); menuButton.href = '#'; - menuButton.alt = MENU_ICON_DESCRIPTION; - menuButton.title = MENU_ICON_DESCRIPTION; + menuButton.alt = '<%js:menu_button_open_description%>'; + menuButton.title = '<%js:menu_button_open_description%>'; setInnerHtml(menuButton, MENU_ICON); addEvent(menuButton, 'click', menu.a); diff --git a/org.eclipse.help.webapp/plugin.xml b/org.eclipse.help.webapp/plugin.xml index 3ff916510..6e622ab1e 100644 --- a/org.eclipse.help.webapp/plugin.xml +++ b/org.eclipse.help.webapp/plugin.xml @@ -128,6 +128,11 @@ httpcontextId="help"> </servlet> <servlet + alias="/m/index.js" + class="org.eclipse.help.internal.webapp.HelpUiJs" + httpcontextId="help"> + </servlet> + <servlet alias="/*.jsp" class="org.eclipse.equinox.jsp.jasper.registry.JSPFactory:/" httpcontextId="help"> diff --git a/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/HelpUi.java b/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/HelpUi.java new file mode 100644 index 000000000..5de23e15e --- /dev/null +++ b/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/HelpUi.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2021 Holger Voormann and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.help.internal.webapp; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.help.internal.webapp.data.ServletResources; +import org.eclipse.help.internal.webapp.data.UrlUtil; + +public class HelpUi { + + private static final Pattern LET_PATTERN = Pattern.compile("<%(string|html|js):([^%>]++)%>"); //$NON-NLS-1$ + + private static final String[] KEEP_PLACEHOLDERS = { "{0}", "{1}", "{2}" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + /** + * Resolves following kind of placeholders in the given template where {@code property_name} is the name of the + * property to retrieve from the <a href="WebappResources.properties">{@code WebappResources.properties}</a> file: + * <ul> + * <li>{@code <%string:property_name%>} - (plain) localized string</li> + * <li>{@code <%html:property_name%>} - HTML encoded localized string</li> + * <li>{@code <%js:property_name%>} - JavaScript encoded localized string</li> + * </ul> + * @param template the template containing placeholders to resolve + * @param request the request object to get the language/locale from to localize Strings + * @return the input template in which the placeholders were resolved + */ + public static String resolve(String template, HttpServletRequest request) { + final Matcher matcher = LET_PATTERN.matcher(template); + // when template loaded/cached: reset(); + boolean found = matcher.find(); + if (found) { + StringBuilder result = new StringBuilder(); + do { + String func = matcher.group(1); + String param = matcher.group(2); + if ("string".equals(func)) { //$NON-NLS-1$ + String resolved = ServletResources.getString(param, KEEP_PLACEHOLDERS, request); + append(matcher, result, resolved); + } else if ("html".equals(func)) { //$NON-NLS-1$ + String resolved = ServletResources.getString(param, KEEP_PLACEHOLDERS, request); + append(matcher, result, UrlUtil.htmlEncode(resolved)); + } else if ("js".equals(func)) { //$NON-NLS-1$ + String resolved = ServletResources.getString(param, KEEP_PLACEHOLDERS, request); + String encoded = UrlUtil.JavaScriptEncode(resolved).replace("\\u0020", " "); //$NON-NLS-1$//$NON-NLS-2$ + append(matcher, result, encoded); + } else { + matcher.appendReplacement(result, ""); //$NON-NLS-1$ + } + found = matcher.find(); + } while (found); + matcher.appendTail(result); + return result.toString(); + } + return template; + } + + private static void append(Matcher matcher, StringBuilder result, String string) { + matcher.appendReplacement(result, Matcher.quoteReplacement(string)); + } + +} diff --git a/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/HelpUiJs.java b/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/HelpUiJs.java new file mode 100644 index 000000000..f07ea4cbd --- /dev/null +++ b/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/HelpUiJs.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2021 Holger Voormann and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.help.internal.webapp; + +import java.io.IOException; +import java.net.URL; +import java.util.Scanner; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.core.runtime.Platform; + +public class HelpUiJs extends HttpServlet { + + private static final long serialVersionUID = 1L; + private static final String JS_TEMPLATE_PROPERTY = "org.eclipse.help.webapp.experimental.ui.js"; //$NON-NLS-1$ + private static final String JS_TEMPLATE_DEFAULT = "org.eclipse.help.webapp/m/index.js"; //$NON-NLS-1$ + private static final String JS_TEMPLATE = loadJsTemplate(); + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); //$NON-NLS-1$ + response.setContentType("text/html; charset=UTF-8"); //$NON-NLS-1$ + response.getWriter().write(HelpUi.resolve(JS_TEMPLATE, request)); + } + + private static String loadJsTemplate() { + String customJsTemplate = System.getProperty(JS_TEMPLATE_PROPERTY); + String jsTemplateLocation = customJsTemplate == null ? JS_TEMPLATE_DEFAULT : customJsTemplate; + try { + return loadTemplate(jsTemplateLocation); + } catch (Exception e) { + String msg = "Failed to load template file for 'index.js': " + jsTemplateLocation; //$NON-NLS-1$ + Platform.getLog(HelpUiJs.class).error(msg, e); + try { + return loadTemplate(JS_TEMPLATE_DEFAULT); + } catch (Exception e2) { + String msg2 = "Failed to load default template file for 'index.js': " + JS_TEMPLATE_DEFAULT; //$NON-NLS-1$ + Platform.getLog(HelpUiJs.class).error(msg2, e2); + return ""; //$NON-NLS-1$ + } + } + } + + private static String loadTemplate(String bundleLocation) throws IOException { + String[] bundleAndPath = bundleLocation.split("/", 2); //$NON-NLS-1$ + URL resourceAsUrl = Platform.getBundle(bundleAndPath[0]).getResource(bundleAndPath[1]); + + // read it as InputStream and convert it to a String + // (by using a Scanner with a delimiter that cannot be found: \A - start of input) + Scanner scanAll = new Scanner(resourceAsUrl.openStream()).useDelimiter("\\A"); //$NON-NLS-1$ + return scanAll.hasNext() ? scanAll.next() : ""; //$NON-NLS-1$ + } + +} diff --git a/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/WebappResources.properties b/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/WebappResources.properties index 3d7ad34f1..f92ae2452 100644 --- a/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/WebappResources.properties +++ b/org.eclipse.help.webapp/src/org/eclipse/help/internal/webapp/WebappResources.properties @@ -238,3 +238,68 @@ cantCreateServlet=Unable to create servlet: {0} # Service API StatusServlet_PARAM_MISSING=The "{0}" parameter is missing from the request. +# modernized help UI +search_field_placeholder=Search +search_field_description=* = any string\n? = any character\n"" = phrase\nAND, OR & NOT = boolean operators +search_page_loading=Loading... +search_indexing=Indexing... {0}% +search_title=Search: {0} +search_scoped_title=Search (in {1}): {0} +searchpage_nothing_found_prefix=No results found for +searchpage_heading=Results +searchpage_heading_scoped=Results in +toc_toggle_button_description=Toggle table of content +back_button_description=Go back one page +forward_button_description=Go forward one page +bookmarks_button_label_close=Cancel +bookmarks_button_description=Bookmarks +bookmarks_button_description_close=Close bookmarks +bookmarks_heading=Bookmarks: +bookmarks_add_field_label=Add: +bookmarks_add_button_description_page=Bookmark current page +bookmarks_add_button_description_search=Bookmark current search +bookmarks_edit_button_label=Edit +bookmarks_edit_button_description=Delete bookmarks +bookmarks_delete_button_label=Delete +bookmarks_delete_button_description=Delete this bookmark (cannot be undone) +bookmarks_delete_all_button_label=Delete all bookmarks +bookmarks_delete_all_button_description=Delete all bookmarks (cannot be undone) +scope_none_label=All +scope_book_label=Book +scope_chapter_label=Chapter +scope_manage_label=Scopes... +scopes_heading=Scopes: +scopes_close_button_label=Cancel +scopes_close_button_description=Close scopes +scopes_new_button_label=New +scopes_new_button_description=Add a new scope +scopes_scope_field_label=Scope: +scopes_scope_create_button_label=Create +scopes_scope_create_button_description=Add new scope +scopes_scope_delete_button_label=Delete +scopes_scope_delete_button_description=Delete this scope +scopes_scope_apply_button_label=Apply +scopes_scope_apply_button_description=Update this scope +scopes_scope_cancel_button_label=Cancel +scopes_scope_cancel_button_description=Go back to list of scopes +menu_button_open_description=Show menu +menu_button_close_description=Hide menu +menu_item_highlighting_label_prefix=Highlight +menu_item_highlighting_label_highlighting=search term +menu_item_highlighting_label_suffix= +menu_item_highlighting_label_description=Toggle search term highlighting +menu_item_font_label=Font: +menu_item_font_plus_button_description=Increase font size +menu_item_font_minus_button_description=Decrease font size +menu_item_bookmarks_label=Bookmarks... +menu_item_bookmarks_description=Bookmark current topic and manage existing bookmarks +menu_item_scopes_label=Search scopes... +menu_item_scopes_description=Manage search scopes +menu_item_print_topic_label=Print topic... +menu_item_print_topic_description=Print topic without its subtopics +menu_item_print_chapter_label=Print chapter... +menu_item_print_chapter_description=Print topic including subtopics +menu_item_help_label=Help +menu_item_help_description=Using the help system +menu_item_about_label=About +menu_item_about_description=Configuration details |