\n';
- html +=
- '
\n';
- html +=
- '
Submit \n';
- html += '
\n';
- html += '
\n';
- html += '
\n';
- html += '
\n';
- html += '
\n';
- html += '
\n';
- html += '
\n';
- html += '
\n';
- html += "\n";
-
- html += '
\n';
- html += '
\n';
- return html;
-}
-
-/**
- * Make the subject string for a forum post
- *
- * @param isReply is this a reply? True or false
- * @param rootPost the original post in the thread, or null
- * @param subjectParam the subject for this post supplied in parameters
- * @return the string
- */
-function makePostSubject(isReply, rootPost, subjectParam) {
- if (isReply && rootPost) {
- return post2text(rootPost.subject);
- }
- return subjectParam;
-}
-
/**
* Make the text (body) string for a forum post
*
- * @param postType the verb such as 'Request', 'Discuss', ...
- * @param value the value that was requested in the root post, or null
+ * @param {PostInfo} pi
* @return the string
*/
-function prefillPostText(postType, value) {
+function prefillPostText(pi) {
+ const postType = pi.postType; // the verb such as 'Request', 'Discuss', ...
+ const value = pi.value; // the value that was requested in the root post, or null
if (postType === cldrForumType.CLOSE) {
return cldrText.get("forum_prefill_close");
} else if (postType === cldrForumType.REQUEST) {
@@ -356,101 +260,20 @@ function prefillPostText(postType, value) {
return "";
}
-/**
- * Open a window displaying the form for creating a post
- *
- * @param html the main html for the form
- * @param text the pre-filled user-editable text for the form
- * @param parentPost the post object, if any, to which this is a reply, for display at the bottom of the window
- *
- * Reference: Bootstrap.js post-modal: https://getbootstrap.com/docs/4.1/components/modal/
- */
-function openPostWindow(html, text, parentPost) {
- const postModal = $("#post-modal");
- postModal.find(".modal-body").html(html);
- $("#post-form textarea[name=text]").val(text);
- if (parentPost) {
- 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"));
- postModal.find(".submit-post").click(submitPost);
- setTimeout(function () {
- postModal.find("textarea").focus();
- }, 1000 /* one second */);
-}
-
-/**
- * Submit a forum post
- *
- * @param event
- */
-function submitPost(event) {
- event.preventDefault();
- event.stopPropagation();
- const form = getFormValues();
- if (formIsAcceptable(form)) {
- $("#post-form button").fadeOut();
- cldrForumPanel.clearCache();
- sendPostRequest(form);
- } else {
- // Call the user's attention to the bogus text area by winking it
- $("#post-form textarea").fadeTo(1000, 0).fadeTo(1000, 1);
- }
-}
-
-/**
- * Is the given form data acceptable?
- *
- * @param {Object} form
- * @returns {Boolean} true if acceptable
- */
-function formIsAcceptable(form) {
- if (!form.text.trim()) {
- // the text field is empty or all whitespace
- return false;
- }
- if (form.postType === cldrForumType.REQUEST) {
- const prefill = cldrText.sub("forum_prefill_request", [form.value]);
- if (form.text.trim() === prefill.trim()) {
- // the text field for a Request matches the pre-fill
- return false;
- }
- }
- return true;
-}
-
-function getFormValues() {
- return {
- text: $("#post-form textarea[name=text]").val(),
- locale: $("#post-form input[name=_]").val(),
- open: $("#post-form input[name=open]").val(),
- postType: $("#post-form input[name=postType]").val(),
- replyTo: $("#post-form input[name=replyTo]").val(),
- root: $("#post-form input[name=root]").val(),
- value: $("#post-form input[name=value]").val(),
- xpath: $("#post-form input[name=xpath]").val(),
- };
-}
-
-function sendPostRequest(form) {
+function sendPostRequest(pi, text) {
const url = cldrStatus.getContextPath() + "/SurveyAjax";
const postData = {
what: "forum_post",
s: cldrStatus.getSessionId(),
- subj: document.getElementById("postSubject").innerHTML,
- _: form.locale,
- open: form.open,
- postType: form.postType,
- replyTo: form.replyTo,
- root: form.root,
- text: form.text,
- value: form.value,
- xpath: form.xpath,
+ subj: pi.subject,
+ _: pi.locale,
+ open: pi.isOpen,
+ postType: pi.postType,
+ replyTo: pi.replyTo,
+ root: pi.replyTo, // root and replyTo are always the same
+ text: text,
+ value: pi.value,
+ xpath: pi.xpstrid,
};
const xhrArgs = {
url: url,
@@ -464,11 +287,8 @@ function sendPostRequest(form) {
function loadHandlerForSubmit(data) {
if (data.err) {
- const post = $(".post").first();
- post.before("
error: " + data.err + "
");
+ cldrNotify.error("Error posting to Forum", data.err);
} else if (data.ret && data.ret.length > 0) {
- const postModal = $("#post-modal");
- postModal.modal("hide");
if (cldrStatus.getCurrentSpecial() === "forum") {
cldrLoad.reloadV(); // main Forum page
} else {
@@ -476,16 +296,14 @@ function loadHandlerForSubmit(data) {
cldrSurvey.expediteStatusUpdate(); // update forum icons (👁️🗨️, 💬) in the main table
}
} else {
- const post = $(".post").first();
- post.before(
- "
Your post was added, #" + data.postId + " but could not be shown. "
- );
+ const message =
+ "Your post was added, #" + data.postId + " but could not be shown.";
+ cldrNotify.error("Error posting to Forum", message);
}
}
function errorHandlerForSubmit(err) {
- const post = $(".post").first();
- post.before("
error! " + err + "
");
+ cldrNotify.error("Error posting to Forum", err);
}
/**
@@ -496,14 +314,12 @@ function errorHandlerForSubmit(err) {
*
* @return new DOM object
*
- * TODO: shorten this function by moving code into subroutines. Also, postpone creating
- * DOM elements until finished constructing the filtered list of threads, to make the code
- * cleaner, faster, and more testable. If context is 'summary', all DOM element creation here
- * is a waste of time.
+ * NOTE: this method is too long and has tech debt. Probably it will be replaced completely
+ * by modern code using Vue components rather than direct DOM manipulation.
*
- * Threading has been revised, so that the same locale+path can have multiple distinct threads,
- * rather than always combining posts with the same locale+path into a single "thread".
- * Reference: https://unicode-org.atlassian.net/browse/CLDR-13695
+ * If context is 'summary', all DOM element creation here is a waste of time.
+ *
+ * The same locale+path can have multiple distinct threads.
*/
function parseContent(posts, context) {
const opts = getOptionsForContext(context);
@@ -516,8 +332,9 @@ function parseContent(posts, context) {
/*
* create the topic (thread) divs -- populate topicDivs with DOM elements
*
- * TODO: skip this loop if opts.createDomElements is false. Currently we have to do this even
- * if opts.createDomElements if false, since filterAndAssembleForumThreads depends on topicDivs.
+ * If this code were to be refactored, ideally it would skip this loop if opts.createDomElements is false.
+ * Currently we have to do this even if opts.createDomElements is false, since filterAndAssembleForumThreads
+ * depends on topicDivs.
*/
for (let num in posts) {
const post = posts[num];
@@ -878,16 +695,27 @@ function addNewPostButtons(el, locale, couldFlag, xpstrid, code, value) {
el.appendChild(document.createElement("p"));
}
Object.keys(options).forEach(function (postType) {
- el.appendChild(
- makeOneNewPostButton(
- postType,
- options[postType],
- locale,
- couldFlag,
- xpstrid,
- code,
- value
- )
+ const label = makePostTypeLabel(postType, false /* isReply */);
+ // A "new post" button has type cldrForumType.REQUEST or cldrForumType.DISCUSS.
+ // REQUEST is only enabled if there is a non-null value (which the user voted for).
+ const disabled = postType === cldrForumType.REQUEST && value === null;
+ // Only an enabled REQUEST button can really cause a path to be flagged.
+ const willFlag =
+ couldFlag && postType === cldrForumType.REQUEST && !disabled;
+ const pi = new PostInfo(postType);
+ const xpathMap = cldrSurvey.getXpathMap();
+ xpathMap.get(
+ {
+ hex: xpstrid,
+ },
+ function (o) {
+ const subject = makeSubject(code, xpstrid, o, xpathMap, willFlag);
+ pi.setLocalePathValueSubject(locale, xpstrid, value, subject);
+ if (willFlag) {
+ pi.setWillFlagTrue();
+ }
+ addPostVueButton(el, pi, label, disabled);
+ }
);
});
}
@@ -909,75 +737,42 @@ function addReplyButtons(el, rootPost) {
);
Object.keys(options).forEach(function (postType) {
- el.appendChild(makeOneReplyButton(rootPost, postType, options[postType]));
+ const pi = new PostInfo(postType);
+ pi.setReplyTo(rootPost);
+ const typeLabel = makePostTypeLabel(postType, true /* isReply */);
+ addPostVueButton(el, pi, typeLabel, false /* not disabled */);
});
}
-function makeOneNewPostButton(
- postType,
- label,
- locale,
- couldFlag,
- xpstrid,
- code,
- value
-) {
- // A "new post" button has type cldrForumType.REQUEST or cldrForumType.DISCUSS.
- // REQUEST is only enabled if there is a non-null value (which the user voted for).
- const disabled = postType === cldrForumType.REQUEST && value === null;
- // Only an enabled REQUEST button can really cause a path to be flagged.
- const willFlag = couldFlag && postType === cldrForumType.REQUEST && !disabled;
- const buttonClass = willFlag
- ? "addPostButton forumNewPostFlagButton btn btn-default btn-sm"
- : "addPostButton forumNewButton btn btn-default btn-sm";
-
- const newButton = forumCreateChunk(label, "button", buttonClass);
- if (disabled) {
- newButton.disabled = true;
- } else {
- cldrDom.listenFor(newButton, "click", function (e) {
- const xpathMap = cldrSurvey.getXpathMap();
- xpathMap.get(
- {
- hex: xpstrid,
- },
- function (o) {
- let subj = code + " " + xpstrid;
- if (o.result && o.result.ph) {
- subj = xpathMap.formatPathHeader(o.result.ph);
- }
- if (willFlag) {
- subj += " (Flag for review)";
- }
- const pi = new PostInfo(postType);
- pi.setLocalePathValueSubject(locale, xpstrid, value, subj);
- if (willFlag) {
- pi.setWillFlagTrue();
- }
- openPostOrReply(pi);
- }
- );
- cldrEvent.stopPropagation(e);
- return false;
- });
+function makeSubject(code, xpstrid, o, xpathMap, willFlag) {
+ let subj = code + " " + xpstrid;
+ if (o.result && o.result.ph) {
+ subj = xpathMap.formatPathHeader(o.result.ph);
+ }
+ if (willFlag) {
+ subj += " (Flag for review)";
}
- return newButton;
+ return subj;
}
-function makeOneReplyButton(post, postType, label) {
- const replyButton = forumCreateChunk(
- label,
- "button",
- "addPostButton btn btn-default btn-sm"
- );
- cldrDom.listenFor(replyButton, "click", function (e) {
- const pi = new PostInfo(postType);
- pi.setReplyTo(post);
- openPostOrReply(pi);
- cldrEvent.stopPropagation(e);
- return false;
- });
- return replyButton;
+function addPostVueButton(containerEl, pi, label, disabled) {
+ try {
+ const wrapper = cldrVue.mount(ForumButton, containerEl);
+ wrapper.setPostInfo(pi);
+ wrapper.setLabel(label);
+ const reminder = cldrText.get(
+ pi.willFlag ? "flag_must_have_reason" : "forum_remember_vote"
+ );
+ wrapper.setReminder(reminder);
+ if (disabled) {
+ wrapper.setDisabled();
+ }
+ } catch (e) {
+ console.error(
+ "Error loading Post Button vue " + e.message + " / " + e.name
+ );
+ cldrNotify.exception(e, "while loading Post Button");
+ }
}
/**
@@ -1508,16 +1303,29 @@ function parseHash(pieces) {
}
}
+let formIsVisible = false;
+
+function isFormVisible() {
+ return formIsVisible;
+}
+
+function setFormIsVisible(visible) {
+ formIsVisible = visible;
+}
+
export {
- FORUM_DIV_CLASS,
SUMMARY_CLASS,
addNewPostButtons,
handleIdChanged,
+ isFormVisible,
load,
parseContent,
parseHash,
+ prefillPostText,
refreshSummary,
reload,
+ sendPostRequest,
+ setFormIsVisible,
setUserCanPost,
/*
* The following are meant to be accessible for unit testing only:
diff --git a/tools/cldr-apps/js/src/esm/cldrForumPanel.mjs b/tools/cldr-apps/js/src/esm/cldrForumPanel.mjs
index 3f2c58c5ac0..ac2efa9e598 100644
--- a/tools/cldr-apps/js/src/esm/cldrForumPanel.mjs
+++ b/tools/cldr-apps/js/src/esm/cldrForumPanel.mjs
@@ -12,6 +12,11 @@ import * as cldrSurvey from "./cldrSurvey.mjs";
import * as cldrTable from "./cldrTable.mjs";
import * as cldrText from "./cldrText.mjs";
+/**
+ * Encapsulate this class name -- caution: it's used literally in surveytool.css
+ */
+const FORUM_DIV_CLASS = "forumDiv";
+
const forumCache = new cldrCache.LRU();
/**
@@ -38,7 +43,7 @@ function loadInfo(frag, tr, theRow) {
cldrForum.setUserCanPost(tr.theTable.json.canModify);
addTopButtons(theRow, frag);
const div = document.createElement("div");
- div.className = cldrForum.FORUM_DIV_CLASS;
+ div.className = FORUM_DIV_CLASS;
const cachedData = forumCache.get(makeCacheKey(theRow.xpstrid));
if (cachedData) {
setPostsFromData(frag, div, cachedData, theRow.xpstrid);
@@ -174,9 +179,9 @@ function updatePosts(tr) {
const content = getForumContent(posts, theRow.xpstrid);
/*
- * Update the first element whose class is cldrForum.FORUM_DIV_CLASS.
+ * Update the first element whose class is FORUM_DIV_CLASS.
*/
- $("." + cldrForum.FORUM_DIV_CLASS)
+ $("." + FORUM_DIV_CLASS)
.first()
.html(content);
}
diff --git a/tools/cldr-apps/js/src/esm/cldrInfo.mjs b/tools/cldr-apps/js/src/esm/cldrInfo.mjs
index 7f9012f9aa4..a19d87dd73b 100644
--- a/tools/cldr-apps/js/src/esm/cldrInfo.mjs
+++ b/tools/cldr-apps/js/src/esm/cldrInfo.mjs
@@ -55,6 +55,7 @@ const INFO_VOTE_TICKET_ID = "info-panel-vote-and-ticket";
const INFO_REGIONAL_ID = "info-panel-regional";
const INFO_FORUM_ID = "info-panel-forum";
const INFO_XPATH_ID = "info-panel-xpath";
+const INFO_BOTTOM_ID = "info-panel-bottom";
/**
* Initialize the Info Panel
@@ -118,6 +119,7 @@ function insertLegacyElement(containerEl) {
appendDiv(el, INFO_REGIONAL_ID);
appendDiv(el, INFO_FORUM_ID);
appendDiv(el, INFO_XPATH_ID);
+ appendDiv(el, INFO_BOTTOM_ID);
}
function appendDiv(el, id) {
@@ -247,6 +249,7 @@ function show(str, tr, hideIfLast, fn) {
addRegionalSidewaysMenu(tr);
addForumPanel(tr);
addXpath(tr);
+ addBottom();
addVoterInfoHover();
}
@@ -452,6 +455,19 @@ function addForumPanel(tr) {
}
}
+/**
+ * An empty paragraph at the bottom of the info panel enables scrolling
+ * to bring the bottom content fully into view without being overlapped
+ * by the xpath shown by addXpath
+ */
+function addBottom() {
+ const el = document.getElementById(INFO_BOTTOM_ID);
+ if (!el) {
+ return;
+ }
+ el.innerHTML = "
";
+}
+
function addXpath(tr) {
const el = document.getElementById(INFO_XPATH_ID);
if (!el) {
diff --git a/tools/cldr-apps/js/src/esm/cldrSurvey.mjs b/tools/cldr-apps/js/src/esm/cldrSurvey.mjs
index 6d6af3e180e..e7e0a0e706f 100644
--- a/tools/cldr-apps/js/src/esm/cldrSurvey.mjs
+++ b/tools/cldr-apps/js/src/esm/cldrSurvey.mjs
@@ -6,6 +6,7 @@ import * as cldrCoverage from "./cldrCoverage.mjs";
import * as cldrCoverageReset from "./cldrCoverageReset.mjs";
import * as cldrDom from "./cldrDom.mjs";
import * as cldrEvent from "./cldrEvent.mjs";
+import * as cldrForum from "./cldrForum.mjs";
import * as cldrGui from "./cldrGui.mjs";
import * as cldrLoad from "./cldrLoad.mjs";
import * as cldrMenu from "./cldrMenu.mjs";
@@ -103,20 +104,16 @@ function getXpathMap() {
/**
* Is the keyboard or input widget 'busy'? i.e., it's a bad time to change the DOM
*
- * @return true if window.getSelection().anchorNode.className contains "dijitInp" or "popover-content",
- * else false
- *
- * "popover-content" identifies the little input window, created using bootstrap, that appears when the
- * user clicks an add ("+") button. Added "popover-content" per https://unicode.org/cldr/trac/ticket/11265.
- *
- * Called only from CldrSurveyVettingLoader.js
+ * @return true if busy
*/
function isInputBusy() {
- if (!window.getSelection) {
- return false;
+ if (cldrForum.isFormVisible()) {
+ return true;
}
- var sel = window.getSelection();
- if (sel && sel.anchorNode && sel.anchorNode.className) {
+ const sel = window.getSelection ? window.getSelection() : null;
+ if (sel?.anchorNode?.className) {
+ // "popover-content" identifies the little input window, created using bootstrap, that appears when the
+ // user clicks an add ("+") button.
if (sel.anchorNode.className.indexOf("popover-content") != -1) {
return true;
}
diff --git a/tools/cldr-apps/js/src/views/ForumButton.vue b/tools/cldr-apps/js/src/views/ForumButton.vue
new file mode 100644
index 00000000000..8c77147368b
--- /dev/null
+++ b/tools/cldr-apps/js/src/views/ForumButton.vue
@@ -0,0 +1,104 @@
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
diff --git a/tools/cldr-apps/js/src/views/ForumForm.vue b/tools/cldr-apps/js/src/views/ForumForm.vue
new file mode 100644
index 00000000000..06c226aba08
--- /dev/null
+++ b/tools/cldr-apps/js/src/views/ForumForm.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
+ {{ pi.subject }}
+ {{ reminder }}
+ {{ pi.postType }}
+
+
+
+
+
+
+
+
+