diff --git a/.eslintrc.json b/.eslintrc.json
index b2d751c9b3..4672a01e70 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -16,6 +16,7 @@
"marked": false,
"moment": false,
"i18n": false,
+ "DynamicText": false,
"bridge": false,
"page_params": false,
"status_classes": false,
diff --git a/static/js/dynamic_text.js b/static/js/dynamic_text.js
new file mode 100644
index 0000000000..1048d2f3fc
--- /dev/null
+++ b/static/js/dynamic_text.js
@@ -0,0 +1,97 @@
+// This is a public instance of the `DynamicText` class.
+// The `DynamicText` object serves to resize text that is set through its
+// public methods so that the text won't ever overrun the parent container.
+var DynamicText = (function () {
+ // the initialization of the `DynamicText` instance.
+ var DynamicText = function ($parent) {
+ if (typeof jQuery !== "undefined" && $parent instanceof jQuery) {
+ // we grab the first element in a jQuery list to run DynamicText on.
+ this.parent = $parent[0];
+ } else if ($parent instanceof Node) {
+ // this is a node rather than a list, so we just take the element given
+ // to run DynamicText on.
+ this.parent = $parent;
+ }
+
+ this.node = document.createElement("span");
+ this.node.style.whiteSpace = "nowrap";
+
+ this.prev_content = this.parent.innerHTML;
+ this.parent.innerHTML = "";
+ this.parent.appendChild(this.node);
+
+ this.update();
+ };
+
+ // an object for private functions that are inaccessible to the outside
+ // world.
+ var internal_funcs = {
+ insertText: function (type, text, node, parent) {
+ if (type === DynamicText.prototype.TYPE.TEXT) {
+ node.innerText = text;
+ } else if (type === DynamicText.prototype.TYPE.HTML) {
+ node.innerHTML = text;
+ } else {
+ blueslip.error("The method '" + type + "' is not a valid " +
+ " DynamicText input method.");
+ }
+
+ // reset the font-size to inherit the parent's size; 1em.
+ node.style.fontSize = "1em";
+
+ var width = {
+ node: node.offsetWidth,
+ parent: parent.clientWidth,
+ };
+
+ // if the width is larger than the parent, resize by the ratio of
+ // the parent's width to the node's width.
+ if (width.node > width.parent) {
+ node.style.fontSize = (width.parent / width.node) + "em";
+ } else {
+ node.style.fontSize = "1em";
+ }
+ },
+ };
+
+ DynamicText.prototype = {
+ // insertion enum types.
+ TYPE: {
+ TEXT: 1,
+ HTML: 2,
+ },
+
+ // re-set the content inside the span element and process for width.
+ // the structure goes like:
+ // FROM: content
+ // TO:
+ // content
+ //
+ update: function () {
+ // call `text` by default since if HTML is needed (unsafe), it can be
+ // done manually.
+ this.text(this.prev_content);
+ },
+
+ // this takes about 0.005ms for items that don't need resizing and 0.08ms
+ // for items that do need resizing.
+ text: function (text) {
+ internal_funcs.insertText(this.TYPE.TEXT, text, this.node, this.parent);
+ },
+
+ // this function takes approx 0.4ms/iteration.
+ // the speed is mostly limited to the slowness of the innerHTML function.
+ html: function (html) {
+ internal_funcs.insertText(this.TYPE.HTML, html, this.node, this.parent);
+ },
+ };
+
+ // keep these arguments updated with `DynamicText` class constructor.
+ return function ($node) {
+ return new DynamicText($node);
+ };
+}());
+
+if (typeof module !== 'undefined') {
+ module.exports = DynamicText;
+}
diff --git a/zproject/settings.py b/zproject/settings.py
index 034d570852..c0c7459b3a 100644
--- a/zproject/settings.py
+++ b/zproject/settings.py
@@ -854,6 +854,7 @@ JS_SPECS = {
'js/feature_flags.js',
'js/loading.js',
'js/util.js',
+ 'js/dynamic_text.js',
'js/rtl.js',
'js/dict.js',
'js/components.js',