This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/app/components/composer-body.js

251 lines
6.8 KiB
JavaScript

import { cancel, later, run, schedule, throttle } from "@ember/runloop";
import discourseComputed, {
bind,
observes,
} from "discourse-common/utils/decorators";
import Component from "@ember/component";
import Composer from "discourse/models/composer";
import KeyEnterEscape from "discourse/mixins/key-enter-escape";
import afterTransition from "discourse/lib/after-transition";
import discourseDebounce from "discourse-common/lib/debounce";
import { headerOffset } from "discourse/lib/offset-calculator";
import positioningWorkaround from "discourse/lib/safari-hacks";
const START_DRAG_EVENTS = ["touchstart", "mousedown"];
const DRAG_EVENTS = ["touchmove", "mousemove"];
const END_DRAG_EVENTS = ["touchend", "mouseup"];
const THROTTLE_RATE = 20;
function mouseYPos(e) {
return e.clientY || (e.touches && e.touches[0] && e.touches[0].clientY);
}
export default Component.extend(KeyEnterEscape, {
elementId: "reply-control",
classNameBindings: [
"composer.creatingPrivateMessage:private-message",
"composeState",
"composer.loading",
"prefixedComposerAction",
"composer.canEditTitle:edit-title",
"composer.createdPost:created-post",
"composer.creatingTopic:topic",
"composer.whisper:composing-whisper",
"composer.sharedDraft:composing-shared-draft",
"showPreview:show-preview:hide-preview",
"currentUserPrimaryGroupClass",
],
@discourseComputed("composer.action")
prefixedComposerAction(action) {
return action ? `composer-action-${action}` : "";
},
@discourseComputed("currentUser.primary_group_name")
currentUserPrimaryGroupClass(primaryGroupName) {
return primaryGroupName && `group-${primaryGroupName}`;
},
@discourseComputed("composer.composeState")
composeState(composeState) {
return composeState || Composer.CLOSED;
},
movePanels(size) {
document.querySelector("#main-outlet").style.paddingBottom = size
? `${size}px`
: "";
// signal the progress bar it should move!
this.appEvents.trigger("composer:resized");
},
@observes("composeState", "composer.{action,canEditTopicFeaturedLink}")
resize() {
schedule("afterRender", () => {
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
discourseDebounce(this, this.debounceMove, 300);
});
},
debounceMove() {
let height = 0;
if (!this.element.classList.contains("saving")) {
height = this.element.offsetHeight;
}
this.movePanels(height);
},
keyUp() {
this.typed();
const lastKeyUp = new Date();
this._lastKeyUp = lastKeyUp;
// One second from now, check to see if the last key was hit when
// we recorded it. If it was, the user paused typing.
cancel(this._lastKeyTimeout);
this._lastKeyTimeout = later(() => {
if (lastKeyUp !== this._lastKeyUp) {
return;
}
this.appEvents.trigger("composer:find-similar");
}, 1000);
},
@observes("composeState")
disableFullscreen() {
if (this.composeState !== Composer.OPEN && positioningWorkaround.blur) {
positioningWorkaround.blur();
}
},
setupComposerResizeEvents() {
this.origComposerSize = 0;
this.lastMousePos = 0;
START_DRAG_EVENTS.forEach((startDragEvent) => {
this.element
.querySelector(".grippie")
?.addEventListener(startDragEvent, this.startDragHandler, {
passive: false,
});
});
if (this._visualViewportResizing()) {
this.viewportResize();
window.visualViewport.addEventListener("resize", this.viewportResize);
}
},
@bind
performDragHandler() {
this.appEvents.trigger("composer:div-resizing");
this.element.classList.add("clear-transitions");
const currentMousePos = mouseYPos(event);
let size = this.origComposerSize + (this.lastMousePos - currentMousePos);
size = Math.min(size, window.innerHeight - headerOffset());
this.movePanels(size);
this.element.style.height = size ? `${size}px` : "";
},
@bind
startDragHandler(event) {
event.preventDefault();
this.origComposerSize = this.element.offsetHeight;
this.lastMousePos = mouseYPos(event);
DRAG_EVENTS.forEach((dragEvent) => {
document.addEventListener(dragEvent, this.throttledPerformDrag);
});
END_DRAG_EVENTS.forEach((endDragEvent) => {
document.addEventListener(endDragEvent, this.endDragHandler);
});
this.appEvents.trigger("composer:resize-started");
},
@bind
endDragHandler() {
this.appEvents.trigger("composer:resize-ended");
DRAG_EVENTS.forEach((dragEvent) => {
document.removeEventListener(dragEvent, this.throttledPerformDrag);
});
END_DRAG_EVENTS.forEach((endDragEvent) => {
document.removeEventListener(endDragEvent, this.endDragHandler);
});
this.element.classList.remove("clear-transitions");
this.element.focus();
},
@bind
throttledPerformDrag(event) {
event.preventDefault();
throttle(this, this.performDragHandler, event, THROTTLE_RATE);
},
@bind
viewportResize() {
const composerVH = window.visualViewport.height * 0.01,
doc = document.documentElement;
doc.style.setProperty("--composer-vh", `${composerVH}px`);
const viewportWindowDiff =
this.windowInnerHeight - window.visualViewport.height;
viewportWindowDiff > 0
? doc.classList.add("keyboard-visible")
: doc.classList.remove("keyboard-visible");
// adds bottom padding when using a hardware keyboard and the accessory bar is visible
// accessory bar height is 55px, using 75 allows a small buffer
doc.style.setProperty(
"--composer-ipad-padding",
`${viewportWindowDiff < 75 ? viewportWindowDiff : 0}px`
);
},
_visualViewportResizing() {
return (
(this.capabilities.isIpadOS || this.site.mobileView) &&
window.visualViewport !== undefined
);
},
didInsertElement() {
this._super(...arguments);
if (this._visualViewportResizing()) {
this.set("windowInnerHeight", window.innerHeight);
}
this.setupComposerResizeEvents();
const resize = () => run(() => this.resize());
const triggerOpen = () => {
if (this.get("composer.composeState") === Composer.OPEN) {
this.appEvents.trigger("composer:opened");
}
};
triggerOpen();
afterTransition($(this.element), () => {
resize();
triggerOpen();
});
positioningWorkaround($(this.element));
},
willDestroyElement() {
this._super(...arguments);
if (this._visualViewportResizing()) {
window.visualViewport.removeEventListener("resize", this.viewportResize);
}
START_DRAG_EVENTS.forEach((startDragEvent) => {
this.element
.querySelector(".grippie")
?.removeEventListener(startDragEvent, this.startDragHandler);
});
cancel(this._lastKeyTimeout);
},
click() {
this.openIfDraft();
},
});