mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
DEV: Update ExpandingTextArea component (#25890)
This commit is contained in:
parent
3200e276b7
commit
5dc95eaacc
@ -24,8 +24,9 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<ExpandingTextArea
|
<ExpandingTextArea
|
||||||
@value={{this.buffered.value}}
|
{{on "input" (with-event-value (fn (mut this.buffered.value)))}}
|
||||||
@rows="1"
|
value={{this.buffered.value}}
|
||||||
|
rows="1"
|
||||||
class="site-text-value"
|
class="site-text-value"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import autosize from "autosize";
|
||||||
|
import { modifier } from "ember-modifier";
|
||||||
|
import autoFocus from "discourse/modifiers/auto-focus";
|
||||||
|
|
||||||
|
const resize = modifier((element) => {
|
||||||
|
autosize(element);
|
||||||
|
return () => autosize.destroy(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ExpandingTextArea = <template>
|
||||||
|
<textarea
|
||||||
|
{{autoFocus}}
|
||||||
|
{{resize}}
|
||||||
|
{{! deprecated args: }}
|
||||||
|
autocorrect={{@autocorrect}}
|
||||||
|
class={{@class}}
|
||||||
|
maxlength={{@maxlength}}
|
||||||
|
name={{@name}}
|
||||||
|
rows={{@rows}}
|
||||||
|
value={{@value}}
|
||||||
|
{{(if @input (modifier on "input" @input))}}
|
||||||
|
...attributes
|
||||||
|
></textarea>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export default ExpandingTextArea;
|
@ -1,30 +0,0 @@
|
|||||||
import { TextArea } from "@ember/legacy-built-in-components";
|
|
||||||
import { schedule } from "@ember/runloop";
|
|
||||||
import $ from "jquery";
|
|
||||||
import autosize from "discourse/lib/autosize";
|
|
||||||
import { observes, on } from "discourse-common/utils/decorators";
|
|
||||||
|
|
||||||
export default TextArea.extend({
|
|
||||||
@on("didInsertElement")
|
|
||||||
_startWatching() {
|
|
||||||
schedule("afterRender", () => {
|
|
||||||
$(this.element).focus();
|
|
||||||
autosize(this.element);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
@observes("value")
|
|
||||||
_updateAutosize() {
|
|
||||||
this.element.value = this.value;
|
|
||||||
const event = new Event("autosize:update", {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: false,
|
|
||||||
});
|
|
||||||
this.element.dispatchEvent(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
@on("willDestroyElement")
|
|
||||||
_disableAutosize() {
|
|
||||||
autosize.destroy($(this.element));
|
|
||||||
},
|
|
||||||
});
|
|
@ -42,8 +42,12 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<ExpandingTextArea
|
<ExpandingTextArea
|
||||||
@name="membership-request-template"
|
{{on
|
||||||
@value={{this.model.membership_request_template}}
|
"input"
|
||||||
|
(with-event-value (fn (mut this.model.membership_request_template)))
|
||||||
|
}}
|
||||||
|
value={{this.model.membership_request_template}}
|
||||||
|
name="membership-request-template"
|
||||||
class="group-form-membership-request-template input-xxlarge"
|
class="group-form-membership-request-template input-xxlarge"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,11 @@
|
|||||||
{{i18n "groups.membership_request.reason"}}
|
{{i18n "groups.membership_request.reason"}}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<ExpandingTextArea @value={{this.reason}} @maxlength="5000" />
|
<ExpandingTextArea
|
||||||
|
{{on "input" (with-event-value (fn (mut this.reason)))}}
|
||||||
|
value={{this.reason}}
|
||||||
|
maxlength="5000"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</:body>
|
</:body>
|
||||||
|
|
||||||
|
@ -1,216 +0,0 @@
|
|||||||
const set =
|
|
||||||
typeof Set === "function"
|
|
||||||
? new Set()
|
|
||||||
: (function () {
|
|
||||||
const list = [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
has(key) {
|
|
||||||
return Boolean(list.includes(key));
|
|
||||||
},
|
|
||||||
add(key) {
|
|
||||||
list.push(key);
|
|
||||||
},
|
|
||||||
delete(key) {
|
|
||||||
list.splice(list.indexOf(key), 1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
function assign(ta, { setOverflowX = true, setOverflowY = true } = {}) {
|
|
||||||
if (!ta || !ta.nodeName || ta.nodeName !== "TEXTAREA" || set.has(ta)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let heightOffset = null;
|
|
||||||
let overflowY = null;
|
|
||||||
let clientWidth = ta.clientWidth;
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
const style = window.getComputedStyle(ta, null);
|
|
||||||
|
|
||||||
overflowY = style.overflowY;
|
|
||||||
|
|
||||||
if (style.resize === "vertical") {
|
|
||||||
ta.style.resize = "none";
|
|
||||||
} else if (style.resize === "both") {
|
|
||||||
ta.style.resize = "horizontal";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style.boxSizing === "content-box") {
|
|
||||||
heightOffset = -(
|
|
||||||
parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
heightOffset =
|
|
||||||
parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
|
||||||
}
|
|
||||||
// Fix when a textarea is not on document body and heightOffset is Not a Number
|
|
||||||
if (isNaN(heightOffset)) {
|
|
||||||
heightOffset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeOverflow(value) {
|
|
||||||
{
|
|
||||||
// Chrome/Safari-specific fix:
|
|
||||||
// When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
|
|
||||||
// made available by removing the scrollbar. The following forces the necessary text reflow.
|
|
||||||
const width = ta.style.width;
|
|
||||||
ta.style.width = "0px";
|
|
||||||
// Force reflow:
|
|
||||||
/* jshint ignore:start */
|
|
||||||
ta.offsetWidth;
|
|
||||||
/* jshint ignore:end */
|
|
||||||
ta.style.width = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
overflowY = value;
|
|
||||||
|
|
||||||
if (setOverflowY) {
|
|
||||||
ta.style.overflowY = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
resize();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resize() {
|
|
||||||
const htmlTop = window.pageYOffset;
|
|
||||||
const bodyTop = document.body.scrollTop;
|
|
||||||
const originalHeight = ta.style.height;
|
|
||||||
|
|
||||||
ta.style.height = "auto";
|
|
||||||
|
|
||||||
let endHeight = ta.scrollHeight + heightOffset;
|
|
||||||
|
|
||||||
if (ta.scrollHeight === 0) {
|
|
||||||
// If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
|
|
||||||
ta.style.height = originalHeight;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ta.style.height = endHeight + "px";
|
|
||||||
|
|
||||||
// used to check if an update is actually necessary on window.resize
|
|
||||||
clientWidth = ta.clientWidth;
|
|
||||||
|
|
||||||
// prevents scroll-position jumping
|
|
||||||
document.documentElement.scrollTop = htmlTop;
|
|
||||||
document.body.scrollTop = bodyTop;
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
const startHeight = ta.style.height;
|
|
||||||
|
|
||||||
resize();
|
|
||||||
|
|
||||||
const style = window.getComputedStyle(ta, null);
|
|
||||||
|
|
||||||
if (style.height !== ta.style.height) {
|
|
||||||
if (overflowY !== "visible") {
|
|
||||||
changeOverflow("visible");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (overflowY !== "hidden") {
|
|
||||||
changeOverflow("hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startHeight !== ta.style.height) {
|
|
||||||
const event = new Event("autosize:resized", {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: false,
|
|
||||||
});
|
|
||||||
ta.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageResize = () => {
|
|
||||||
if (ta.clientWidth !== clientWidth) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const destroy = (style) => {
|
|
||||||
window.removeEventListener("resize", pageResize, false);
|
|
||||||
ta.removeEventListener("input", update, false);
|
|
||||||
ta.removeEventListener("keyup", update, false);
|
|
||||||
ta.removeEventListener("autosize:destroy", destroy, false);
|
|
||||||
ta.removeEventListener("autosize:update", update, false);
|
|
||||||
set.delete(ta);
|
|
||||||
|
|
||||||
Object.keys(style).forEach((key) => {
|
|
||||||
ta.style[key] = style[key];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ta.addEventListener("autosize:destroy", destroy, false);
|
|
||||||
|
|
||||||
// IE9 does not fire onpropertychange or oninput for deletions,
|
|
||||||
// so binding to onkeyup to catch most of those events.
|
|
||||||
// There is no way that I know of to detect something like 'cut' in IE9.
|
|
||||||
if ("onpropertychange" in ta && "oninput" in ta) {
|
|
||||||
ta.addEventListener("keyup", update, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("resize", pageResize, false);
|
|
||||||
ta.addEventListener("input", update, false);
|
|
||||||
ta.addEventListener("autosize:update", update, false);
|
|
||||||
set.add(ta);
|
|
||||||
|
|
||||||
if (setOverflowX) {
|
|
||||||
ta.style.overflowX = "hidden";
|
|
||||||
ta.style.wordWrap = "break-word";
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportDestroy(ta) {
|
|
||||||
if (!(ta && ta.nodeName && ta.nodeName === "TEXTAREA")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = new Event("autosize:destroy", {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: false,
|
|
||||||
});
|
|
||||||
ta.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportUpdate(ta) {
|
|
||||||
if (!(ta && ta.nodeName && ta.nodeName === "TEXTAREA")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = new Event("autosize:update", {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: false,
|
|
||||||
});
|
|
||||||
ta.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
let autosize = (el, options) => {
|
|
||||||
if (el) {
|
|
||||||
Array.prototype.forEach.call(el.length ? el : [el], (x) =>
|
|
||||||
assign(x, options)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
autosize.destroy = (el) => {
|
|
||||||
if (el) {
|
|
||||||
Array.prototype.forEach.call(el.length ? el : [el], exportDestroy);
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
autosize.update = (el) => {
|
|
||||||
if (el) {
|
|
||||||
Array.prototype.forEach.call(el.length ? el : [el], exportUpdate);
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default autosize;
|
|
@ -59,6 +59,7 @@
|
|||||||
"@uppy/xhr-upload": "3.1.1",
|
"@uppy/xhr-upload": "3.1.1",
|
||||||
"a11y-dialog": "8.0.4",
|
"a11y-dialog": "8.0.4",
|
||||||
"admin": "1.0.0",
|
"admin": "1.0.0",
|
||||||
|
"autosize": "^6.0.1",
|
||||||
"babel-import-util": "^2.0.1",
|
"babel-import-util": "^2.0.1",
|
||||||
"babel-plugin-ember-template-compilation": "^2.2.1",
|
"babel-plugin-ember-template-compilation": "^2.2.1",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
|
@ -3084,6 +3084,11 @@ atob@^2.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||||
|
|
||||||
|
autosize@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/autosize/-/autosize-6.0.1.tgz#64ee78dd7029be959eddd3afbbd33235b957e10f"
|
||||||
|
integrity sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==
|
||||||
|
|
||||||
available-typed-arrays@^1.0.5:
|
available-typed-arrays@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||||
|
Loading…
Reference in New Issue
Block a user