mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FIX: refreshes post toolbar on topic scroll (#26228)
This commit now ensures we will properly attempt to refresh the toolbar position after a scroll and consider it as a selection change. Tangential to this fix we improved the positioning on mobile to better account for the native menu position and avoid a situation where the toolbar is always behind the native menu and can't be used.
This commit is contained in:
parent
0bf87c5ed8
commit
80d2afa316
@ -1,10 +1,11 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { cached, tracked } from "@glimmer/tracking";
|
import { cached, tracked } from "@glimmer/tracking";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { cancel } from "@ember/runloop";
|
import { cancel, debounce } from "@ember/runloop";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { modifier } from "ember-modifier";
|
import { modifier } from "ember-modifier";
|
||||||
import PostTextSelectionToolbar from "discourse/components/post-text-selection-toolbar";
|
import PostTextSelectionToolbar from "discourse/components/post-text-selection-toolbar";
|
||||||
|
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
||||||
import toMarkdown from "discourse/lib/to-markdown";
|
import toMarkdown from "discourse/lib/to-markdown";
|
||||||
import {
|
import {
|
||||||
selectedNode,
|
selectedNode,
|
||||||
@ -62,9 +63,15 @@ export default class PostTextSelection extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
appEventsListeners = modifier(() => {
|
appEventsListeners = modifier(() => {
|
||||||
|
this.appEvents.on("topic:current-post-scrolled", this, "handleTopicScroll");
|
||||||
this.appEvents.on("quote-button:quote", this, "insertQuote");
|
this.appEvents.on("quote-button:quote", this, "insertQuote");
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
this.appEvents.off(
|
||||||
|
"topic:current-post-scrolled",
|
||||||
|
this,
|
||||||
|
"handleTopicScroll"
|
||||||
|
);
|
||||||
this.appEvents.off("quote-button:quote", this, "insertQuote");
|
this.appEvents.off("quote-button:quote", this, "insertQuote");
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -72,6 +79,7 @@ export default class PostTextSelection extends Component {
|
|||||||
willDestroy() {
|
willDestroy() {
|
||||||
super.willDestroy(...arguments);
|
super.willDestroy(...arguments);
|
||||||
|
|
||||||
|
cancel(this.debouncedSelectionChanged);
|
||||||
this.menuInstance?.destroy();
|
this.menuInstance?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +89,7 @@ export default class PostTextSelection extends Component {
|
|||||||
await this.menuInstance?.close();
|
await this.menuInstance?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
async selectionChanged(options = {}) {
|
||||||
async selectionChanged() {
|
|
||||||
if (this.isSelecting) {
|
if (this.isSelecting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -102,6 +109,7 @@ export default class PostTextSelection extends Component {
|
|||||||
// it's also generally unecessary work to go
|
// it's also generally unecessary work to go
|
||||||
// through this if the selection hasn't changed
|
// through this if the selection hasn't changed
|
||||||
if (
|
if (
|
||||||
|
!options.force &&
|
||||||
this.menuInstance?.expanded &&
|
this.menuInstance?.expanded &&
|
||||||
this.prevSelectedText === _selectedText
|
this.prevSelectedText === _selectedText
|
||||||
) {
|
) {
|
||||||
@ -184,7 +192,26 @@ export default class PostTextSelection extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
let offset = 3;
|
||||||
|
if (this.shouldRenderUnder) {
|
||||||
|
// on mobile, we ideally want to show the toolbar at the end of the selection
|
||||||
|
offset = 20;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isElementInViewport(selectedRange().startContainer.parentNode) ||
|
||||||
|
!isElementInViewport(selectedRange().endContainer.parentNode)
|
||||||
|
) {
|
||||||
|
// we force a higher offset in two cases:
|
||||||
|
// - the start of the selection is not in viewport, in this case on iOS for example
|
||||||
|
// the native menu will be shown at the bottom of the screen, right after text selection
|
||||||
|
// so we need more space
|
||||||
|
// - the end of the selection is not in viewport, in this case our menu will be shown at the top
|
||||||
|
// of the screen, so we need more space to avoid overlapping with the native menu
|
||||||
|
offset = 70;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuOptions = {
|
||||||
identifier: "post-text-selection-toolbar",
|
identifier: "post-text-selection-toolbar",
|
||||||
component: PostTextSelectionToolbar,
|
component: PostTextSelectionToolbar,
|
||||||
inline: true,
|
inline: true,
|
||||||
@ -192,7 +219,7 @@ export default class PostTextSelection extends Component {
|
|||||||
fallbackPlacements: this.shouldRenderUnder
|
fallbackPlacements: this.shouldRenderUnder
|
||||||
? ["bottom-end", "top-start"]
|
? ["bottom-end", "top-start"]
|
||||||
: ["bottom-start"],
|
: ["bottom-start"],
|
||||||
offset: this.shouldRenderUnder ? 25 : 3,
|
offset,
|
||||||
trapTab: false,
|
trapTab: false,
|
||||||
closeOnScroll: false,
|
closeOnScroll: false,
|
||||||
data: {
|
data: {
|
||||||
@ -212,7 +239,7 @@ export default class PostTextSelection extends Component {
|
|||||||
|
|
||||||
this.menuInstance = await this.menu.show(
|
this.menuInstance = await this.menu.show(
|
||||||
virtualElementFromTextRange(),
|
virtualElementFromTextRange(),
|
||||||
options
|
menuOptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +294,19 @@ export default class PostTextSelection extends Component {
|
|||||||
return this.site.isMobileDevice || isIOS || isAndroid || isOpera;
|
return this.site.isMobileDevice || isIOS || isAndroid || isOpera;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleTopicScroll() {
|
||||||
|
if (this.site.mobileView) {
|
||||||
|
this.debouncedSelectionChanged = debounce(
|
||||||
|
this,
|
||||||
|
this.selectionChanged,
|
||||||
|
{ force: true },
|
||||||
|
250,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async insertQuote() {
|
async insertQuote() {
|
||||||
await this.args.selectText();
|
await this.args.selectText();
|
||||||
|
Loading…
Reference in New Issue
Block a user