diff --git a/tools/cldr-apps/js/src/esm/cldrCache.mjs b/tools/cldr-apps/js/src/esm/cldrCache.mjs new file mode 100644 index 00000000000..5dd3d0ef0d8 --- /dev/null +++ b/tools/cldr-apps/js/src/esm/cldrCache.mjs @@ -0,0 +1,42 @@ +/* + * cldrCache: a simple least-recently-used cache for the Survey Tool front end + * + * Based partly on: https://stackoverflow.com/questions/996505/lru-cache-implementation-in-javascript + * post by odinho - Velmont + */ +export class LRU { + constructor(max = 100) { + this._max = max; + this._cache = new Map(); + } + + clear() { + this._cache.clear(); + } + + get(key) { + const val = this._cache.get(key); + if (val) { + // delete and set again so it's most recent + this._cache.delete(key); + this._cache.set(key, val); + } + return val; + } + + set(key, val) { + if (this._cache.has(key)) { + // delete before setting again so it's most recent + this._cache.delete(key); + } else if (this._cache.size == this._max) { + this._cache.delete(this._oldest()); + } + this._cache.set(key, val); + } + + _oldest() { + // Iteration happens in insertion order (chronologically), so the first is the oldest + // Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map + return this._cache.keys().next().value; + } +} diff --git a/tools/cldr-apps/js/src/esm/cldrForum.js b/tools/cldr-apps/js/src/esm/cldrForum.js index ab20874db1b..6388d9f3b7c 100644 --- a/tools/cldr-apps/js/src/esm/cldrForum.js +++ b/tools/cldr-apps/js/src/esm/cldrForum.js @@ -15,6 +15,11 @@ import * as cldrStatus from "./cldrStatus.js"; import * as cldrSurvey from "./cldrSurvey.js"; import * as cldrText from "./cldrText.js"; +/** + * Encapsulate this class name -- caution: it's used literally in surveytool.css + */ +const FORUM_DIV_CLASS = "forumDiv"; + const SUMMARY_CLASS = "getForumSummary"; const FORUM_DEBUG = false; @@ -66,7 +71,7 @@ let userCanPost = false; */ let displayUtc = false; -// called as special.load +// called as special.load; this is for the full-page Forum, not for posts shown in the Info Panel function load() { const curLocale = cldrStatus.getCurrentLocale(); if (!curLocale) { @@ -287,7 +292,7 @@ function makePostHtml( html += "\n"; html += '
\n'; - html += '
\n'; + html += '
\n'; return html; } @@ -345,9 +350,11 @@ function openPostWindow(html, text, parentPost) { postModal.find(".modal-body").html(html); $("#post-form textarea[name=text]").val(text); if (parentPost) { - const forumDiv = parseContent([parentPost], "parent"); - const postHolder = postModal.find(".modal-body").find(".forumDiv"); - postHolder[0].appendChild(forumDiv); + const div = parseContent([parentPost], "parent"); + const postHolder = postModal + .find(".modal-body") + .find("." + FORUM_DIV_CLASS); + postHolder[0].appendChild(div); } postModal.modal(); autosize(postModal.find("textarea")); @@ -378,6 +385,7 @@ function submitPost(event) { */ function reallySubmitPost(text) { $("#post-form button").fadeOut(); + cldrForumPanel.clearCache(); const form = getFormValues(text); sendPostRequest(form); } @@ -871,10 +879,6 @@ function makeOneNewPostButton( code, value ) { - const buttonTitle = couldFlag - ? "forumNewPostFlagButton" - : "forumNewPostButton"; - const buttonClass = couldFlag ? "addPostButton forumNewPostFlagButton btn btn-default btn-sm" : "addPostButton forumNewButton btn btn-default btn-sm"; @@ -1201,11 +1205,11 @@ function filterAndAssembleForumThreads( threadHash, applyFilter ); - const forumDiv = document.createDocumentFragment(); + const div = document.createDocumentFragment(); let countEl = null; if (showThreadCount) { countEl = document.createElement("h4"); - forumDiv.append(countEl); + div.append(countEl); } let threadCount = 0; posts.forEach(function (post) { @@ -1216,7 +1220,7 @@ function filterAndAssembleForumThreads( * from filteredArray to prevent appending the same div again * (which would move the div to the bottom, not duplicate it). */ - forumDiv.append(topicDivs[post.threadId]); + div.append(topicDivs[post.threadId]); filteredArray = filteredArray.filter((id) => id !== post.threadId); } }); @@ -1224,7 +1228,7 @@ function filterAndAssembleForumThreads( countEl.innerHTML = threadCount + (threadCount === 1 ? " topic" : " topics"); } - return forumDiv; + return div; } /** @@ -1462,6 +1466,7 @@ function parseHash(pieces) { } export { + FORUM_DIV_CLASS, SUMMARY_CLASS, addNewPostButtons, handleIdChanged, diff --git a/tools/cldr-apps/js/src/esm/cldrForumPanel.js b/tools/cldr-apps/js/src/esm/cldrForumPanel.js index f98ea287968..fd80bbd3edd 100644 --- a/tools/cldr-apps/js/src/esm/cldrForumPanel.js +++ b/tools/cldr-apps/js/src/esm/cldrForumPanel.js @@ -2,31 +2,63 @@ * cldrForumPanel: encapsulate Survey Tool Forum Info Panel code. */ import * as cldrAjax from "./cldrAjax.js"; +import * as cldrCache from "./cldrCache.mjs"; import * as cldrDom from "./cldrDom.js"; import * as cldrEvent from "./cldrEvent.js"; import * as cldrForum from "./cldrForum.js"; import * as cldrInfo from "./cldrInfo.js"; -import * as cldrLoad from "./cldrLoad.js"; import * as cldrRetry from "./cldrRetry.js"; import * as cldrStatus from "./cldrStatus.js"; import * as cldrSurvey from "./cldrSurvey.js"; import * as cldrTable from "./cldrTable.js"; import * as cldrText from "./cldrText.js"; +const forumCache = new cldrCache.LRU(); + /** * Called when showing the Info Panel each time * - * @param {Node} frag - * @param {Node} forumDivClone = tr.forumDiv.cloneNode(true) - * @param {Node} tr + * @param {Node} frag the fragment node to which we should append + * @param {Node} tr the node for the row currently displayed in the DOM, plus associated data + * @param {Object} theRow data for the row, based (partly) on latest json + * + * All dependencies on tr should be removed from this module. + * Due to tech debt it's hard to tell for this tr object, what (if anything) comes from, or corresponds to + * (1) the current DOM, + * (2) the latest json, + * (3) DOM fragments under construction, + * (4) miscellaneous data items attached to tr although they don't need to be in the DOM... + * Pretend we don't know that theRow === tr.theRow, since the DOM shouldn't be used as a database. + * + * Called by cldrInfo.show */ -function loadInfo(frag, forumDivClone, tr) { - if (tr.theRow) { - addTopButtons(tr.theRow, frag); +function loadInfo(frag, tr, theRow) { + if (!frag || !tr || !theRow) { + return; + } + cldrForum.setUserCanPost(tr.theTable.json.canModify); + addTopButtons(theRow, frag); + const div = document.createElement("div"); + div.className = cldrForum.FORUM_DIV_CLASS; + const cachedData = forumCache.get(makeCacheKey(theRow.xpstrid)); + if (cachedData) { + setPostsFromData(frag, div, cachedData, theRow.xpstrid); + } else { + fetchAndLoadPosts(frag, div, tr, theRow); } +} + +function setPostsFromData(frag, div, data, xpstrid) { + const content = getForumContent(data, xpstrid); + div.appendChild(content); + frag.appendChild(div); +} + +function fetchAndLoadPosts(frag, div, tr, theRow) { const loader2 = cldrDom.createChunk(cldrText.get("loading"), "i"); frag.appendChild(loader2); - const ourUrl = tr.forumDiv.url + "&what=forum_count" + cldrSurvey.cacheKill(); + frag.appendChild(div); + const ourUrl = forumCountUrl(theRow); window.setTimeout(function () { const xhrArgs = { url: ourUrl, @@ -34,7 +66,7 @@ function loadInfo(frag, forumDivClone, tr) { load: function (json) { if (json && json.forum_count !== undefined) { const nrPosts = parseInt(json.forum_count); - havePosts(nrPosts, forumDivClone, tr, loader2); + havePosts(nrPosts, div, tr, loader2); } else { console.log("Some error loading post count??"); } @@ -77,21 +109,20 @@ function getUsersValue(theRow) { return null; } -function havePosts(nrPosts, forumDivClone, tr, loader2) { +function havePosts(nrPosts, div, tr, loader2) { cldrDom.setDisplayed(loader2, false); // not needed - tr.forumDiv.forumPosts = nrPosts; if (nrPosts == 0) { return; // nothing to do } const showButton = cldrDom.createChunk( - "Show " + tr.forumDiv.forumPosts + " posts", + "Show " + nrPosts + " posts", "button", "forumShow" ); - forumDivClone.appendChild(showButton); + div.appendChild(showButton); const theListen = function (e) { cldrDom.setDisplayed(showButton, false); @@ -106,7 +137,7 @@ function havePosts(nrPosts, forumDivClone, tr, loader2) { /** * Update the forum posts in the Info Panel * - * @param tr the table-row element with which the forum posts are associated, + * @param {Node} tr the table-row element with which the forum posts are associated, * and whose info is shown in the Info Panel; or null, to get the * tr from surveyCurrentId */ @@ -117,17 +148,18 @@ function updatePosts(tr) { tr = document.getElementById(rowId); } else { /* - * This is normal when adding a post in the main forum interface, which has no Info Panel). + * This is normal when adding a post in the main forum interface, which has no Info Panel. */ return; } } - if (!tr || !tr.forumDiv || !tr.forumDiv.url) { + if (!tr?.theRow) { return; } - let ourUrl = tr.forumDiv.url + "&what=forum_fetch"; + const theRow = tr.theRow; + const ourUrl = forumFetchUrl(theRow); - let errorHandler = function (err) { + function errorHandler(err) { console.log("Error in updatePosts: " + err); const message = cldrStatus.stopIcon() + @@ -136,33 +168,21 @@ function updatePosts(tr) { ""; cldrInfo.showWithRow(message, tr); cldrRetry.handleDisconnect("Could not load for updatePosts:" + err, null); - }; + } - let loadHandler = function (json) { + function loadHandler(json) { try { if (json && json.ret && json.ret.length > 0) { const posts = json.ret; - let content = cldrForum.parseContent(posts, "info"); - /* - * Reality check: the json should refer to the same path as tr, which in practice - * always matches cldrStatus.getCurrentId(). If not, log a warning and substitute "Please reload" - * for the content. - */ - const xpstrid = posts[0].xpath; - if (xpstrid !== tr.xpstrid || xpstrid !== cldrStatus.getCurrentId()) { - console.log( - "Warning: xpath strid mismatch in updatePosts loadHandler:" - ); - console.log("posts[0].xpath = " + posts[0].xpath); - console.log("tr.xpstrid = " + tr.xpstrid); - console.log("surveyCurrentId = " + cldrStatus.getCurrentId()); - - content = "Please reload"; - } + forumCache.set(makeCacheKey(theRow.xpstrid), posts); + const content = getForumContent(posts, theRow.xpstrid); + /* - * Update the element whose class is 'forumDiv'. + * Update the first element whose class is cldrForum.FORUM_DIV_CLASS. */ - $(".forumDiv").first().html(content); + $("." + cldrForum.FORUM_DIV_CLASS) + .first() + .html(content); } } catch (e) { console.log("Error in ajax forum read ", e.message); @@ -171,7 +191,7 @@ function updatePosts(tr) { cldrStatus.stopIcon() + " exception in ajax forum read: " + e.message; cldrInfo.showWithRow(message, tr); } - }; + } const xhrArgs = { url: ourUrl, @@ -182,38 +202,46 @@ function updatePosts(tr) { cldrAjax.sendXhr(xhrArgs); } -/** - * Called when initially setting up the section. - * - * @param {Node} tr - * @param {Node} theRow - * @param {Node} forumDiv - */ -function appendForumStuff(tr, theRow, forumDiv) { - cldrForum.setUserCanPost(tr.theTable.json.canModify); +function getForumContent(posts, xpstridExpected) { + /* + * Reality check: the json should refer to the same path as tr, which in practice + * always matches cldrStatus.getCurrentId(). If not, log a warning and substitute "Please reload" + * for the content. + */ + const xpstrid = posts[0].xpath; + if (xpstrid !== xpstridExpected || xpstrid !== cldrStatus.getCurrentId()) { + console.log("Warning: xpath strid mismatch in updatePosts loadHandler:"); + console.log("posts[0].xpath = " + posts[0].xpath); + console.log("xpstridExpected = " + xpstridExpected); + console.log("surveyCurrentId = " + cldrStatus.getCurrentId()); + return "Please reload"; + } + return cldrForum.parseContent(posts, "info"); +} - cldrDom.removeAllChildNodes(forumDiv); // we may be updating. - const locmap = cldrLoad.getTheLocaleMap(); - var theForum = locmap.getLanguage(cldrStatus.getCurrentLocale()); - forumDiv.replyStub = +function forumCountUrl(theRow) { + return ( cldrStatus.getContextPath() + - "/survey?forum=" + - theForum + + "/SurveyAjax?what=forum_count" + + "&xpath=" + + theRow.xpathId + "&_=" + cldrStatus.getCurrentLocale() + - "&replyto="; - forumDiv.postUrl = forumDiv.replyStub + "x" + theRow; - /* - * Note: SurveyAjax requires a "what" parameter for SurveyAjax. - * It is not supplied here, but may be added later with code such as: - * let ourUrl = tr.forumDiv.url + "&what=forum_count" + cacheKill() ; - * let ourUrl = tr.forumDiv.url + "&what=forum_fetch"; - * Unfortunately that means "what" is not the first argument, as it would - * be ideally for human readability of request urls. - */ - forumDiv.url = + "&fhash=" + + theRow.rowHash + + "&vhash=" + + "&s=" + + cldrStatus.getSessionId() + + "&voteinfo=t" + + cldrSurvey.cacheKill() + ); +} + +function forumFetchUrl(theRow) { + return ( cldrStatus.getContextPath() + - "/SurveyAjax?xpath=" + + "/SurveyAjax?what=forum_fetch" + + "&xpath=" + theRow.xpathId + "&_=" + cldrStatus.getCurrentLocale() + @@ -221,8 +249,18 @@ function appendForumStuff(tr, theRow, forumDiv) { theRow.rowHash + "&vhash=" + "&s=" + - tr.theTable.session + - "&voteinfo=t"; + cldrStatus.getSessionId() + + "&voteinfo=t" + + cldrSurvey.cacheKill() + ); +} + +function makeCacheKey(xpstrid) { + return cldrStatus.getCurrentLocale() + "-" + xpstrid; +} + +function clearCache() { + forumCache.clear(); } -export { loadInfo, appendForumStuff, updatePosts }; +export { clearCache, loadInfo, updatePosts }; diff --git a/tools/cldr-apps/js/src/esm/cldrInfo.js b/tools/cldr-apps/js/src/esm/cldrInfo.js index 146327574f6..dbad8eecc3c 100644 --- a/tools/cldr-apps/js/src/esm/cldrInfo.js +++ b/tools/cldr-apps/js/src/esm/cldrInfo.js @@ -263,21 +263,12 @@ function show(str, tr, hideIfLast, fn) { if (tr && tr.ticketLink) { fragment.appendChild(tr.ticketLink.cloneNode(true)); } - - // forum stuff - if (tr && tr.forumDiv) { - cldrSideways.loadMenu(fragment, tr); - /* - * The name forumDivClone is a reminder that forumDivClone !== tr.forumDiv. - * TODO: explain the reason for using cloneNode here, rather than using - * tr.forumDiv directly. Would it work as well to set tr.forumDiv = forumDivClone, - * after cloning? - */ - var forumDivClone = tr.forumDiv.cloneNode(true); - cldrForumPanel.loadInfo(fragment, forumDivClone, tr); // give a chance to update anything else - fragment.appendChild(forumDivClone); + if (tr) { + cldrSideways.loadMenu(fragment, tr.xpstrid); // regional variants (sibling locales) + } + if (tr?.theRow && !cldrStatus.isVisitor()) { + cldrForumPanel.loadInfo(fragment, tr, tr.theRow); } - if (tr && tr.theRow && tr.theRow.xpath) { fragment.appendChild( cldrDom.clickToSelect( @@ -285,7 +276,7 @@ function show(str, tr, hideIfLast, fn) { ) ); } - var pucontent = document.getElementById("itemInfo"); + const pucontent = document.getElementById("itemInfo"); if (!pucontent) { console.log("itemInfo not found in show!"); return; @@ -890,7 +881,14 @@ function addJumpToOriginal(theRow, el) { } } +function clearCachesAndReload() { + cldrForumPanel.clearCache(); + cldrSideways.clearCache(); + cldrLoad.reloadV(); +} + export { + clearCachesAndReload, closePanel, initialize, listen, diff --git a/tools/cldr-apps/js/src/esm/cldrSideways.js b/tools/cldr-apps/js/src/esm/cldrSideways.js index fa810a7992b..47d3be6ff1b 100644 --- a/tools/cldr-apps/js/src/esm/cldrSideways.js +++ b/tools/cldr-apps/js/src/esm/cldrSideways.js @@ -3,6 +3,7 @@ * which enables switching and comparing between a set of related locales, * such as: aa = Afar, aa_DJ = Afar (Djibouti), and aa_ER = Afar (Eritrea) */ +import * as cldrCache from "./cldrCache.mjs"; import * as cldrDom from "./cldrDom.js"; import * as cldrEvent from "./cldrEvent.js"; import * as cldrLoad from "./cldrLoad.js"; @@ -13,200 +14,240 @@ import * as cldrText from "./cldrText.js"; /** * Array storing all only-1 sublocale */ -let oneLocales = []; +const oneLocales = []; /** * Timeout for showing sideways view */ let sidewaysShowTimeout = -1; -function loadMenu(frag, tr) { - if (oneLocales[cldrStatus.getCurrentLocale()]) { +const sidewaysCache = new cldrCache.LRU(); + +const SIDEWAYS_AREA_CLASS = "sidewaysArea"; + +function loadMenu(frag, xpstrid) { + const curLocale = cldrStatus.getCurrentLocale(); + if (!curLocale || oneLocales[curLocale]) { return; } + const cachedData = sidewaysCache.get(makeCacheKey(curLocale, xpstrid)); + if (cachedData) { + const sidewaysControl = document.createElement("div"); + sidewaysControl.className = SIDEWAYS_AREA_CLASS; + frag.appendChild(sidewaysControl); + setMenuFromData(sidewaysControl, cachedData); + } else { + fetchAndLoadMenu(frag, curLocale, xpstrid); + } +} + +function fetchAndLoadMenu(frag, curLocale, xpstrid) { const sidewaysControl = cldrDom.createChunk( cldrText.get("sideways_loading0"), "div", - "sidewaysArea" + SIDEWAYS_AREA_CLASS ); frag.appendChild(sidewaysControl); - - function clearMyTimeout() { - if (sidewaysShowTimeout != -1) { - // https://www.w3schools.com/jsref/met_win_clearinterval.asp - window.clearInterval(sidewaysShowTimeout); - sidewaysShowTimeout = -1; - } - } clearMyTimeout(); sidewaysShowTimeout = window.setTimeout(function () { clearMyTimeout(); - cldrDom.updateIf(sidewaysControl, cldrText.get("sideways_loading1")); - const curLocale = cldrStatus.getCurrentLocale(); - if (!curLocale) { + if ( + curLocale !== cldrStatus.getCurrentLocale() || + xpstrid !== cldrStatus.getCurrentId() + ) { return; } - var url = - cldrStatus.getContextPath() + - "/SurveyAjax?what=getsideways&_=" + - curLocale + - "&s=" + - cldrStatus.getSessionId() + - "&xpath=" + - tr.theRow.xpstrid + - cldrSurvey.cacheKill(); - cldrLoad.myLoad(url, "sidewaysView", function (json) { - /* - * Count the number of unique locales in json.others and json.novalue. - */ - var relatedLocales = json.novalue.slice(); - for (var s in json.others) { - for (var t in json.others[s]) { - relatedLocales[json.others[s][t]] = true; - } + cldrDom.updateIf(sidewaysControl, cldrText.get("sideways_loading1")); + cldrLoad.myLoad( + getSidewaysUrl(curLocale, xpstrid), + "sidewaysView", + function (json) { + sidewaysCache.set(makeCacheKey(curLocale, xpstrid), json); + setMenuFromData(sidewaysControl, json); } - // if there is 1 sublocale (+ 1 default), we do nothing - if (Object.keys(relatedLocales).length <= 2) { - oneLocales[cldrStatus.getCurrentLocale()] = true; - cldrDom.updateIf(sidewaysControl, ""); - } else { - if (!json.others) { - cldrDom.updateIf(sidewaysControl, ""); // no sibling locales (or all null?) - } else { - cldrDom.updateIf(sidewaysControl, ""); // remove string - - var topLocale = json.topLocale; - const locmap = cldrLoad.getTheLocaleMap(); - var curLocale = locmap.getRegionAndOrVariantName(topLocale); - var readLocale = null; - - // merge the read-only sublocale to base locale - var mergeReadBase = function mergeReadBase(list) { - var baseValue = null; - // find the base locale, remove it and store its value - for (var l = 0; l < list.length; l++) { - var loc = list[l][0]; - if (loc === topLocale) { - baseValue = list[l][1]; - list.splice(l, 1); - break; - } - } - - // replace the default locale(read-only) with base locale, store its name for label - for (var l = 0; l < list.length; l++) { - var loc = list[l][0]; - var bund = locmap.getLocaleInfo(loc); - if (bund && bund.readonly) { - readLocale = locmap.getRegionAndOrVariantName(loc); - list[l][0] = topLocale; - list[l][1] = baseValue; - break; - } - } - }; - - // compare all sublocale values - var appendLocaleList = function appendLocaleList(list, curValue) { - var group = document.createElement("optGroup"); - var br = document.createElement("optGroup"); - group.appendChild(br); - - group.setAttribute("label", "Regional Variants for " + curLocale); - group.setAttribute("title", "Regional Variants for " + curLocale); - - var escape = "\u00A0\u00A0\u00A0"; - var unequalSign = "\u2260\u00A0"; - - for (var l = 0; l < list.length; l++) { - var loc = list[l][0]; - var title = list[l][1]; - var item = document.createElement("option"); - item.setAttribute("value", loc); - if (title == null) { - item.setAttribute("title", "undefined"); - } else { - item.setAttribute("title", title); - } - - var str = locmap.getRegionAndOrVariantName(loc); - if (loc === topLocale) { - str = str + " (= " + readLocale + ")"; - } - - if (loc === cldrStatus.getCurrentLocale()) { - str = escape + str; - item.setAttribute("selected", "selected"); - item.setAttribute("disabled", "disabled"); - } else if (title != curValue) { - str = unequalSign + str; - } else { - str = escape + str; - } - item.appendChild(document.createTextNode(str)); - group.appendChild(item); - } - popupSelect.appendChild(group); - }; - - var dataList = []; - - var popupSelect = document.createElement("select"); - for (var s in json.others) { - for (var t in json.others[s]) { - dataList.push([json.others[s][t], s]); - } + ); + }, 2000); // wait 2 seconds before loading this. +} + +function clearMyTimeout() { + if (sidewaysShowTimeout != -1) { + // https://www.w3schools.com/jsref/met_win_clearinterval.asp + window.clearInterval(sidewaysShowTimeout); + sidewaysShowTimeout = -1; + } +} + +function getSidewaysUrl(curLocale, xpstrid) { + return ( + cldrStatus.getContextPath() + + "/SurveyAjax?what=getsideways&_=" + + curLocale + + "&s=" + + cldrStatus.getSessionId() + + "&xpath=" + + xpstrid + + cldrSurvey.cacheKill() + ); +} + +function setMenuFromData(sidewaysControl, json) { + /* + * Count the number of unique locales in json.others and json.novalue. + */ + var relatedLocales = json.novalue.slice(); + for (var s in json.others) { + for (var t in json.others[s]) { + relatedLocales[json.others[s][t]] = true; + } + } + // if there is 1 sublocale (+ 1 default), we do nothing + if (Object.keys(relatedLocales).length <= 2) { + oneLocales[cldrStatus.getCurrentLocale()] = true; + cldrDom.updateIf(sidewaysControl, ""); + } else { + if (!json.others) { + cldrDom.updateIf(sidewaysControl, ""); // no sibling locales (or all null?) + } else { + cldrDom.updateIf(sidewaysControl, ""); // remove string + + var topLocale = json.topLocale; + const locmap = cldrLoad.getTheLocaleMap(); + var curLocale = locmap.getRegionAndOrVariantName(topLocale); + var readLocale = null; + + // merge the read-only sublocale to base locale + var mergeReadBase = function mergeReadBase(list) { + var baseValue = null; + // find the base locale, remove it and store its value + for (var l = 0; l < list.length; l++) { + var loc = list[l][0]; + if (loc === topLocale) { + baseValue = list[l][1]; + list.splice(l, 1); + break; } + } - /* - * Set curValue = the value for cldrStatus.getCurrentLocale() - */ - var curValue = null; - for (let l = 0; l < dataList.length; l++) { - var loc = dataList[l][0]; - if (loc === cldrStatus.getCurrentLocale()) { - curValue = dataList[l][1]; - break; - } + // replace the default locale(read-only) with base locale, store its name for label + for (var l = 0; l < list.length; l++) { + var loc = list[l][0]; + var bund = locmap.getLocaleInfo(loc); + if (bund && bund.readonly) { + readLocale = locmap.getRegionAndOrVariantName(loc); + list[l][0] = topLocale; + list[l][1] = baseValue; + break; } - /* - * Force the use of unequalSign in the regional comparison pop-up for locales in - * json.novalue, by assigning a value that's different from curValue. - */ - if (json.novalue) { - const differentValue = curValue === "A" ? "B" : "A"; // anything different from curValue - for (s in json.novalue) { - dataList.push([json.novalue[s], differentValue]); - } + } + }; + + // compare all sublocale values + var appendLocaleList = function appendLocaleList(list, curValue) { + var group = document.createElement("optGroup"); + var br = document.createElement("optGroup"); + group.appendChild(br); + + group.setAttribute("label", "Regional Variants for " + curLocale); + group.setAttribute("title", "Regional Variants for " + curLocale); + + var escape = "\u00A0\u00A0\u00A0"; + var unequalSign = "\u2260\u00A0"; + + for (var l = 0; l < list.length; l++) { + var loc = list[l][0]; + var title = list[l][1]; + var item = document.createElement("option"); + item.setAttribute("value", loc); + if (title == null) { + item.setAttribute("title", "undefined"); + } else { + item.setAttribute("title", title); } - mergeReadBase(dataList); - - // then sort by sublocale name - dataList = dataList.sort(function (a, b) { - return ( - locmap.getRegionAndOrVariantName(a[0]) > - locmap.getRegionAndOrVariantName(b[0]) - ); - }); - appendLocaleList(dataList, curValue); - - var group = document.createElement("optGroup"); - popupSelect.appendChild(group); - - cldrDom.listenFor(popupSelect, "change", function (e) { - var newLoc = popupSelect.value; - if (newLoc !== cldrStatus.getCurrentLocale()) { - cldrStatus.setCurrentLocale(newLoc); - cldrLoad.reloadV(); - } - return cldrEvent.stopPropagation(e); - }); - - sidewaysControl.appendChild(popupSelect); + + var str = locmap.getRegionAndOrVariantName(loc); + if (loc === topLocale) { + str = str + " (= " + readLocale + ")"; + } + + if (loc === cldrStatus.getCurrentLocale()) { + str = escape + str; + item.setAttribute("selected", "selected"); + item.setAttribute("disabled", "disabled"); + } else if (title != curValue) { + str = unequalSign + str; + } else { + str = escape + str; + } + item.appendChild(document.createTextNode(str)); + group.appendChild(item); + } + popupSelect.appendChild(group); + }; + + var dataList = []; + + var popupSelect = document.createElement("select"); + for (var s in json.others) { + for (var t in json.others[s]) { + dataList.push([json.others[s][t], s]); } } - }); - }, 2000); // wait 2 seconds before loading this. + + /* + * Set curValue = the value for cldrStatus.getCurrentLocale() + */ + var curValue = null; + for (let l = 0; l < dataList.length; l++) { + var loc = dataList[l][0]; + if (loc === cldrStatus.getCurrentLocale()) { + curValue = dataList[l][1]; + break; + } + } + /* + * Force the use of unequalSign in the regional comparison pop-up for locales in + * json.novalue, by assigning a value that's different from curValue. + */ + if (json.novalue) { + const differentValue = curValue === "A" ? "B" : "A"; // anything different from curValue + for (s in json.novalue) { + dataList.push([json.novalue[s], differentValue]); + } + } + mergeReadBase(dataList); + + // then sort by sublocale name + dataList = dataList.sort(function (a, b) { + return ( + locmap.getRegionAndOrVariantName(a[0]) > + locmap.getRegionAndOrVariantName(b[0]) + ); + }); + appendLocaleList(dataList, curValue); + + var group = document.createElement("optGroup"); + popupSelect.appendChild(group); + + cldrDom.listenFor(popupSelect, "change", function (e) { + var newLoc = popupSelect.value; + if (newLoc !== cldrStatus.getCurrentLocale()) { + cldrStatus.setCurrentLocale(newLoc); + cldrLoad.reloadV(); + } + return cldrEvent.stopPropagation(e); + }); + + sidewaysControl.appendChild(popupSelect); + } + } +} + +function makeCacheKey(curLocale, xpstrid) { + return curLocale + "-" + xpstrid; +} + +function clearCache() { + sidewaysCache.clear(); } -export { loadMenu }; +export { clearCache, loadMenu }; diff --git a/tools/cldr-apps/js/src/esm/cldrTable.js b/tools/cldr-apps/js/src/esm/cldrTable.js index c84eae16f4a..e1ac15409aa 100644 --- a/tools/cldr-apps/js/src/esm/cldrTable.js +++ b/tools/cldr-apps/js/src/esm/cldrTable.js @@ -12,7 +12,6 @@ import * as cldrAjax from "./cldrAjax.js"; import * as cldrCoverage from "./cldrCoverage.js"; import * as cldrDom from "./cldrDom.js"; import * as cldrEvent from "./cldrEvent.js"; -import * as cldrForumPanel from "./cldrForumPanel.js"; import * as cldrGui from "./cldrGui.js"; import * as cldrInfo from "./cldrInfo.js"; import * as cldrLoad from "./cldrLoad.js"; @@ -376,8 +375,8 @@ function getPageUrl(curLocale, curPage, curId) { /** * Update one row using data received from server. * - * @param tr the table row - * @param theRow the data for the row + * @param {Node} tr the table row + * @param {Object} theRow the data for the row * * Cells (columns) in each row: * Code English Abstain A Winning Add Others @@ -698,14 +697,8 @@ function updateRowCodeCell(tr, theRow, cell) { codeStr = codeStr + " (optional)"; } cell.appendChild(cldrDom.createChunk(codeStr)); - if (cldrStatus.getSurveyUser()) { - cell.className = "d-code codecell"; - if (!tr.forumDiv) { - tr.forumDiv = document.createElement("div"); - tr.forumDiv.className = "forumDiv"; - } - cldrForumPanel.appendForumStuff(tr, theRow, tr.forumDiv); - } + cell.className = "d-code codecell"; + // extra attributes if ( theRow.extraAttributes && diff --git a/tools/cldr-apps/js/src/views/InfoPanel.vue b/tools/cldr-apps/js/src/views/InfoPanel.vue index 39fe50f7140..067316d9eaf 100644 --- a/tools/cldr-apps/js/src/views/InfoPanel.vue +++ b/tools/cldr-apps/js/src/views/InfoPanel.vue @@ -9,6 +9,9 @@ ✕ Info Panel + @@ -21,6 +24,10 @@ export default { closeInfoPanel() { cldrInfo.closePanel(); }, + + reloadInfoPanel() { + cldrInfo.clearCachesAndReload(); + }, }, }; diff --git a/tools/cldr-apps/js/test/TestCldrCache.mjs b/tools/cldr-apps/js/test/TestCldrCache.mjs new file mode 100644 index 00000000000..37082a830a7 --- /dev/null +++ b/tools/cldr-apps/js/test/TestCldrCache.mjs @@ -0,0 +1,53 @@ +import * as cldrCache from "../src/esm/cldrCache.mjs"; + +export const TestCldrc = "ok"; + +const assert = chai.assert; + +describe("cldrCache.LRU", function () { + const c = new cldrCache.LRU(3); + + it("should work up to its capacity", function () { + c.clear(); + c.set("1", "test1"); + c.set("2", "test2"); + c.set("3", "test3"); + const val1 = c.get("1"); + const val2 = c.get("2"); + const val3 = c.get("3"); + assert( + val1 === "test1" && val2 === "test2" && val3 === "test3", + "should get 3 items that were set but got " + + val1 + + " " + + val2 + + " " + + val3 + ); + }); + + it("should discard LRU", function () { + c.set("4", "test4"); // 1 should be discarded + assert(c.get("4") === "test4", "should get MRU"); + assert( + c.get("1") === undefined, + "should not get LRU that should have been discarded" + ); + }); + + it("should clear", function () { + c.clear(); + assert(c.get("4") === undefined, "should not get anything after clear"); + }); + + it("should revise LRU for get", function () { + c.set("A", "testA"); + c.set("B", "testB"); + c.set("C", "testC"); + c.get("A"); // now A should be MRU and B should be LRU + c.set("D", "testD"); // B (not A) should get discarded + assert(c.get("B") === undefined, "should have discarded B"); + assert(c.get("A") === "testA", "should not have discarded A"); + assert(c.get("D") === "testD", "should have added D"); + }); +}); diff --git a/tools/cldr-apps/js/test/index.js b/tools/cldr-apps/js/test/index.js index 05b07eec459..5d922c838d3 100644 --- a/tools/cldr-apps/js/test/index.js +++ b/tools/cldr-apps/js/test/index.js @@ -1,6 +1,7 @@ import { TestCldrAccount } from "./TestCldrAccount.js"; import { TestCldrAdmin } from "./TestCldrAdmin.js"; import { TestCldrBulkClosePosts } from "./TestCldrBulkClosePosts.js"; +import { TestCldrCache } from "./TestCldrCache.mjs"; import { TestCldrChecksum } from "./TestCldrChecksum.js"; import { TestCldrCreateLogin } from "./TestCldrCreateLogin.js"; import { TestCldrCsvFromTable } from "./TestCldrCsvFromTable.js"; @@ -22,6 +23,7 @@ export default { TestCldrAccount, TestCldrAdmin, TestCldrBulkClosePosts, + TestCldrCache, TestCldrChecksum, TestCldrCreateLogin, TestCldrCsvFromTable, diff --git a/tools/cldr-apps/js/test/pretty.sh b/tools/cldr-apps/js/test/pretty.sh index 1097f854342..29e49063904 100644 --- a/tools/cldr-apps/js/test/pretty.sh +++ b/tools/cldr-apps/js/test/pretty.sh @@ -1,8 +1,9 @@ -npx prettier --write tools/cldr-apps/js/src/*.js \ +npx prettier --no-error-on-unmatched-pattern --write \ + tools/cldr-apps/js/src/*.{js,mjs} \ tools/cldr-apps/js/src/css/*.css \ tools/cldr-apps/js/src/esm/*.{js,mjs} \ tools/cldr-apps/js/src/views/*.vue \ - tools/cldr-apps/js/test/*.js \ - tools/cldr-apps/js/test/nonbrowser/*.js \ + tools/cldr-apps/js/test/*.{js,mjs} \ + tools/cldr-apps/js/test/nonbrowser/*.{js,mjs} \ tools/cldr-apps/js/test/*.html \ - tools/cldr-apps/js/util/*.mjs + tools/cldr-apps/js/util/*.{js,mjs}