mirror of
https://github.com/discourse/discourse.git
synced 2026-08-17 16:35:03 -05:00
DEV: Convert quote-button to a glimmer component (#22787)
Native class, glimmer, no jQ, async/await Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
This commit is contained in:
co-authored by
Isaac Janzen
parent
25138db433
commit
62d3979870
@@ -14,6 +14,6 @@
|
||||
@label="composer.save_edit"
|
||||
@translatedTitle={{this.buttonTitle}}
|
||||
@isLoading={{this.isSaving}}
|
||||
@disabled={{eq @initialValue this.value}}
|
||||
@disabled={{this.disabled}}
|
||||
/>
|
||||
</div>
|
||||
@@ -15,11 +15,19 @@ export default class FastEdit extends Component {
|
||||
modifier: translateModKey("Meta+"),
|
||||
});
|
||||
|
||||
get disabled() {
|
||||
return this.value === this.args.initialValue;
|
||||
}
|
||||
|
||||
@action
|
||||
onKeydown(event) {
|
||||
if (event.key === "Escape") {
|
||||
this.args.close();
|
||||
} else if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
|
||||
} else if (
|
||||
event.key === "Enter" &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
!this.disabled
|
||||
) {
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,71 @@
|
||||
<div class="buttons">
|
||||
{{#if this.embedQuoteButton}}
|
||||
<DButton
|
||||
@class="btn-flat insert-quote"
|
||||
@action={{this.insertQuote}}
|
||||
@icon="quote-left"
|
||||
@label="post.quote_reply"
|
||||
@title="post.quote_reply_shortcut"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.siteSettings.enable_fast_edit}}
|
||||
{{#if this._canEditPost}}
|
||||
<div
|
||||
{{did-insert this.didInsert}}
|
||||
class={{concat-class
|
||||
"quote-button"
|
||||
(if this.visible "visible")
|
||||
(if this.displayFastEditInput "fast-editing")
|
||||
(if this.animated "animated")
|
||||
}}
|
||||
>
|
||||
<div class="buttons">
|
||||
{{#if this.embedQuoteButton}}
|
||||
<DButton
|
||||
@icon="pencil-alt"
|
||||
@action={{this._toggleFastEditForm}}
|
||||
@label="post.quote_edit"
|
||||
@class="btn-flat quote-edit-label"
|
||||
@title="post.quote_edit_shortcut"
|
||||
@action={{this.insertQuote}}
|
||||
@icon="quote-left"
|
||||
@label="post.quote_reply"
|
||||
@title="post.quote_reply_shortcut"
|
||||
class="btn-flat insert-quote"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.quoteSharingEnabled}}
|
||||
<span class="quote-sharing">
|
||||
{{#if this.quoteSharingShowLabel}}
|
||||
<DButton
|
||||
@icon="share"
|
||||
@label="post.quote_share"
|
||||
@class="btn-flat quote-share-label"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if this.canEditPost}}
|
||||
<DButton
|
||||
@icon="pencil-alt"
|
||||
@action={{this.toggleFastEditForm}}
|
||||
@label="post.quote_edit"
|
||||
@title="post.quote_edit_shortcut"
|
||||
class="btn-flat quote-edit-label"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<span class="quote-share-buttons">
|
||||
{{#each this.quoteSharingSources as |source|}}
|
||||
{{#if this.quoteSharingEnabled}}
|
||||
<span class="quote-sharing">
|
||||
{{#if this.quoteSharingShowLabel}}
|
||||
<DButton
|
||||
@class="btn-flat"
|
||||
@action={{action "share" source}}
|
||||
@translatedTitle={{source.title}}
|
||||
@icon={{source.icon}}
|
||||
@icon="share"
|
||||
@label="post.quote_share"
|
||||
class="btn-flat quote-share-label"
|
||||
/>
|
||||
{{/each}}
|
||||
<PluginOutlet
|
||||
@name="quote-share-buttons-after"
|
||||
@connectorTagName="div"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<span class="quote-share-buttons">
|
||||
{{#each this.quoteSharingSources as |source|}}
|
||||
<DButton
|
||||
@action={{fn this.share source}}
|
||||
@translatedTitle={{source.title}}
|
||||
@icon={{source.icon}}
|
||||
class="btn-flat"
|
||||
/>
|
||||
{{/each}}
|
||||
|
||||
<PluginOutlet
|
||||
@name="quote-share-buttons-after"
|
||||
@connectorTagName="span"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="extra">
|
||||
{{#if this._displayFastEditInput}}
|
||||
<FastEdit
|
||||
@initialValue={{this._fastEditInitialSelection}}
|
||||
@post={{this.post}}
|
||||
@close={{this._hideButton}}
|
||||
/>
|
||||
{{/if}}
|
||||
<div class="extra">
|
||||
{{#if this.displayFastEditInput}}
|
||||
<FastEdit
|
||||
@initialValue={{this.fastEditInitialSelection}}
|
||||
@post={{this.post}}
|
||||
@close={{this.hideButton}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet @name="quote-button-after" @connectorTagName="div" />
|
||||
<PluginOutlet @name="quote-button-after" @connectorTagName="div" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,17 +1,17 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import {
|
||||
postUrl,
|
||||
selectedElement,
|
||||
selectedNode,
|
||||
selectedRange,
|
||||
selectedText,
|
||||
setCaretPosition,
|
||||
} from "discourse/lib/utilities";
|
||||
import Component from "@ember/component";
|
||||
import { INPUT_DELAY } from "discourse-common/config/environment";
|
||||
import Sharing from "discourse/lib/sharing";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import discourseComputed, { bind } from "discourse-common/utils/decorators";
|
||||
import { action } from "@ember/object";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { getAbsoluteURL } from "discourse-common/lib/get-url";
|
||||
import { next, schedule } from "@ember/runloop";
|
||||
@@ -42,88 +42,90 @@ export function fixQuotes(str) {
|
||||
return str.replace(/[\u201C\u201D]/g, '"').replace(/[\u2018\u2019]/g, "'");
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
modal: service(),
|
||||
export default class QuoteButton extends Component {
|
||||
@service appEvents;
|
||||
@service capabilities;
|
||||
@service currentUser;
|
||||
@service modal;
|
||||
@service site;
|
||||
@service siteSettings;
|
||||
|
||||
classNames: ["quote-button"],
|
||||
classNameBindings: [
|
||||
"visible",
|
||||
"_displayFastEditInput:fast-editing",
|
||||
"animated",
|
||||
],
|
||||
visible: false,
|
||||
animated: false,
|
||||
privateCategory: alias("topic.category.read_restricted"),
|
||||
editPost: null,
|
||||
_popper: null,
|
||||
popperPlacement: "top-start",
|
||||
popperOffset: [0, 3],
|
||||
@tracked visible = false;
|
||||
@tracked animated = false;
|
||||
@tracked isFastEditable = false;
|
||||
@tracked displayFastEditInput = false;
|
||||
@tracked fastEditInitialSelection;
|
||||
|
||||
_isFastEditable: false,
|
||||
_displayFastEditInput: false,
|
||||
_fastEditInitialSelection: null,
|
||||
_canEditPost: false,
|
||||
|
||||
_isMouseDown: false,
|
||||
_reselected: false,
|
||||
isMouseDown = false;
|
||||
reselected = false;
|
||||
prevSelection;
|
||||
element;
|
||||
popper;
|
||||
popperPlacement = "top-start";
|
||||
popperOffset = [0, 3];
|
||||
|
||||
@bind
|
||||
_hideButton() {
|
||||
this.quoteState.clear();
|
||||
this.set("visible", false);
|
||||
this.set("animated", false);
|
||||
hideButton() {
|
||||
this.args.quoteState.clear();
|
||||
|
||||
this.set("_isFastEditable", false);
|
||||
this.set("_displayFastEditInput", false);
|
||||
this.set("_fastEditInitialSelection", null);
|
||||
this._teardownSelectionListeners();
|
||||
},
|
||||
this.visible = false;
|
||||
this.animated = false;
|
||||
this.isFastEditable = false;
|
||||
this.displayFastEditInput = false;
|
||||
this.fastEditInitialSelection = null;
|
||||
|
||||
_selectionChanged() {
|
||||
if (this._displayFastEditInput) {
|
||||
this.teardownSelectionListeners();
|
||||
}
|
||||
|
||||
selectionChanged() {
|
||||
if (this.displayFastEditInput) {
|
||||
this.textRange = virtualElementFromTextRange();
|
||||
return;
|
||||
}
|
||||
|
||||
const quoteState = this.quoteState;
|
||||
|
||||
const selection = window.getSelection();
|
||||
if (selection.isCollapsed) {
|
||||
if (this.visible) {
|
||||
this._hideButton();
|
||||
this.hideButton();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure we selected content inside 1 post *only*
|
||||
let firstRange, postId;
|
||||
let postId;
|
||||
for (let r = 0; r < selection.rangeCount; r++) {
|
||||
const range = selection.getRangeAt(r);
|
||||
const $selectionStart = $(range.startContainer);
|
||||
const $ancestor = $(range.commonAncestorContainer);
|
||||
const selectionStart =
|
||||
range.startContainer.nodeType === Node.ELEMENT_NODE
|
||||
? range.startContainer
|
||||
: range.startContainer.parentElement;
|
||||
const ancestor =
|
||||
range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE
|
||||
? range.commonAncestorContainer
|
||||
: range.commonAncestorContainer.parentElement;
|
||||
|
||||
if ($selectionStart.closest(".cooked").length === 0) {
|
||||
if (!selectionStart.closest(".cooked")) {
|
||||
return;
|
||||
}
|
||||
|
||||
firstRange = firstRange || range;
|
||||
postId = postId || $ancestor.closest(".boxed, .reply").data("post-id");
|
||||
postId ||= ancestor.closest(".boxed, .reply")?.dataset?.postId;
|
||||
|
||||
if ($ancestor.closest(".contents").length === 0 || !postId) {
|
||||
if (!ancestor.closest(".contents") || !postId) {
|
||||
if (this.visible) {
|
||||
this._hideButton();
|
||||
this.hideButton();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const _selectedElement = selectedElement();
|
||||
const _selectedElement =
|
||||
selectedNode().nodeType === Node.ELEMENT_NODE
|
||||
? selectedNode()
|
||||
: selectedNode().parentElement;
|
||||
const _selectedText = selectedText();
|
||||
|
||||
const $selectedElement = $(_selectedElement);
|
||||
const cooked =
|
||||
$selectedElement.find(".cooked")[0] ||
|
||||
$selectedElement.closest(".cooked")[0];
|
||||
_selectedElement.querySelector(".cooked") ||
|
||||
_selectedElement.closest(".cooked");
|
||||
|
||||
// computing markdown takes a lot of time on long posts
|
||||
// this code attempts to compute it only when we can't fast track
|
||||
@@ -147,40 +149,37 @@ export default Component.extend({
|
||||
}
|
||||
}
|
||||
|
||||
const quoteState = this.args.quoteState;
|
||||
quoteState.selected(postId, _selectedText, opts);
|
||||
this.set("visible", quoteState.buffer.length > 0);
|
||||
this.visible = quoteState.buffer.length > 0;
|
||||
|
||||
if (this.siteSettings.enable_fast_edit) {
|
||||
this.set("_canEditPost", this.post?.can_edit);
|
||||
if (this.canEditPost) {
|
||||
const regexp = new RegExp(escapeRegExp(quoteState.buffer), "gi");
|
||||
const matches = cooked.innerHTML.match(regexp);
|
||||
const non_ascii_regex = /[^\x00-\x7F]/;
|
||||
|
||||
if (this._canEditPost) {
|
||||
const regexp = new RegExp(escapeRegExp(quoteState.buffer), "gi");
|
||||
const matches = cooked.innerHTML.match(regexp);
|
||||
const non_ascii_regex = /[^\x00-\x7F]/;
|
||||
|
||||
if (
|
||||
quoteState.buffer.length < 1 ||
|
||||
quoteState.buffer.includes("|") || // tables are too complex
|
||||
quoteState.buffer.match(/\n/g) || // linebreaks are too complex
|
||||
matches?.length > 1 || // duplicates are too complex
|
||||
non_ascii_regex.test(quoteState.buffer) // non-ascii chars break fast-edit
|
||||
) {
|
||||
this.set("_isFastEditable", false);
|
||||
this.set("_fastEditInitialSelection", null);
|
||||
} else if (matches?.length === 1) {
|
||||
this.set("_isFastEditable", true);
|
||||
this.set("_fastEditInitialSelection", quoteState.buffer);
|
||||
}
|
||||
if (
|
||||
quoteState.buffer.length === 0 ||
|
||||
quoteState.buffer.includes("|") || // tables are too complex
|
||||
quoteState.buffer.match(/\n/g) || // linebreaks are too complex
|
||||
matches?.length > 1 || // duplicates are too complex
|
||||
non_ascii_regex.test(quoteState.buffer) // non-ascii chars break fast-edit
|
||||
) {
|
||||
this.isFastEditable = false;
|
||||
this.fastEditInitialSelection = null;
|
||||
} else if (matches?.length === 1) {
|
||||
this.isFastEditable = true;
|
||||
this.fastEditInitialSelection = quoteState.buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// avoid hard loops in quote selection unconditionally
|
||||
// this can happen if you triple click text in firefox
|
||||
if (this._prevSelection === _selectedText) {
|
||||
if (this.prevSelection === _selectedText) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._prevSelection = _selectedText;
|
||||
this.prevSelection = _selectedText;
|
||||
|
||||
// on Desktop, shows the button at the beginning of the selection
|
||||
// on Mobile, shows the button at the end of the selection
|
||||
@@ -200,9 +199,9 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
this.textRange = virtualElementFromTextRange();
|
||||
this._setupSelectionListeners();
|
||||
this.setupSelectionListeners();
|
||||
|
||||
this._popper = createPopper(this.textRange, this.element, {
|
||||
this.popper = createPopper(this.textRange, this.element, {
|
||||
placement: this.popperPlacement,
|
||||
modifiers: [
|
||||
{
|
||||
@@ -223,162 +222,174 @@ export default Component.extend({
|
||||
if (!this.animated) {
|
||||
// We only enable CSS transitions after the initial positioning
|
||||
// otherwise the button can appear to fly in from off-screen
|
||||
next(() => this.set("animated", true));
|
||||
next(() => (this.animated = true));
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@bind
|
||||
_updateRect() {
|
||||
updateRect() {
|
||||
this.textRange?.updateRect();
|
||||
},
|
||||
}
|
||||
|
||||
_setupSelectionListeners() {
|
||||
document.body.addEventListener("mouseup", this._updateRect);
|
||||
window.addEventListener("scroll", this._updateRect);
|
||||
document.scrollingElement.addEventListener("scroll", this._updateRect);
|
||||
},
|
||||
setupSelectionListeners() {
|
||||
document.body.addEventListener("mouseup", this.updateRect);
|
||||
window.addEventListener("scroll", this.updateRect);
|
||||
document.scrollingElement.addEventListener("scroll", this.updateRect);
|
||||
}
|
||||
|
||||
_teardownSelectionListeners() {
|
||||
document.body.removeEventListener("mouseup", this._updateRect);
|
||||
window.removeEventListener("scroll", this._updateRect);
|
||||
document.scrollingElement.removeEventListener("scroll", this._updateRect);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
teardownSelectionListeners() {
|
||||
document.body.removeEventListener("mouseup", this.updateRect);
|
||||
window.removeEventListener("scroll", this.updateRect);
|
||||
document.scrollingElement.removeEventListener("scroll", this.updateRect);
|
||||
}
|
||||
|
||||
@bind
|
||||
onSelectionChanged() {
|
||||
const { isWinphone, isAndroid } = this.capabilities;
|
||||
const wait = isWinphone || isAndroid ? INPUT_DELAY : 25;
|
||||
const onSelectionChanged = () => {
|
||||
discourseDebounce(this, this._selectionChanged, wait);
|
||||
};
|
||||
discourseDebounce(this, this.selectionChanged, wait);
|
||||
}
|
||||
|
||||
$(document)
|
||||
.on("mousedown.quote-button", (e) => {
|
||||
this._prevSelection = null;
|
||||
this._isMouseDown = true;
|
||||
this._reselected = false;
|
||||
@bind
|
||||
mousedown(e) {
|
||||
this.prevSelection = null;
|
||||
this.isMouseDown = true;
|
||||
this.reselected = false;
|
||||
|
||||
// prevents fast-edit input event to trigger mousedown
|
||||
if (e.target.classList.contains("fast-edit-input")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$(e.target).closest(".quote-button, .create, .share, .reply-new")
|
||||
.length === 0
|
||||
) {
|
||||
this._hideButton();
|
||||
}
|
||||
})
|
||||
.on("mouseup.quote-button", (e) => {
|
||||
// prevents fast-edit input event to trigger mouseup
|
||||
if (e.target.classList.contains("fast-edit-input")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._prevSelection = null;
|
||||
this._isMouseDown = false;
|
||||
onSelectionChanged();
|
||||
})
|
||||
.on("selectionchange.quote-button", () => {
|
||||
if (!this._isMouseDown && !this._reselected) {
|
||||
onSelectionChanged();
|
||||
}
|
||||
});
|
||||
this.appEvents.on("quote-button:quote", this, "insertQuote");
|
||||
this.appEvents.on("quote-button:edit", this, "_toggleFastEditForm");
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._popper?.destroy();
|
||||
$(document)
|
||||
.off("mousedown.quote-button")
|
||||
.off("mouseup.quote-button")
|
||||
.off("selectionchange.quote-button");
|
||||
this.appEvents.off("quote-button:quote", this, "insertQuote");
|
||||
this.appEvents.off("quote-button:edit", this, "_toggleFastEditForm");
|
||||
this._teardownSelectionListeners();
|
||||
},
|
||||
|
||||
@computed("topic", "quoteState.postId")
|
||||
get post() {
|
||||
return this.topic.postStream.findLoadedPost(this.quoteState.postId);
|
||||
},
|
||||
|
||||
@discourseComputed("topic.{isPrivateMessage,invisible,category}")
|
||||
quoteSharingEnabled(topic) {
|
||||
if (
|
||||
this.site.mobileView ||
|
||||
this.siteSettings.share_quote_visibility === "none" ||
|
||||
(this.currentUser &&
|
||||
this.siteSettings.share_quote_visibility === "anonymous") ||
|
||||
this.quoteSharingSources.length === 0 ||
|
||||
this.privateCategory ||
|
||||
(this.currentUser && topic.invisible)
|
||||
) {
|
||||
return false;
|
||||
// prevents fast-edit input event from triggering mousedown
|
||||
if (e.target.classList.contains("fast-edit-input")) {
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
if (!e.target.closest(".quote-button, .create, .share, .reply-new")) {
|
||||
this.hideButton();
|
||||
}
|
||||
}
|
||||
|
||||
@discourseComputed("topic.isPrivateMessage")
|
||||
quoteSharingSources(isPM) {
|
||||
@bind
|
||||
mouseup(e) {
|
||||
// prevents fast-edit input event from triggering mouseup
|
||||
if (e.target.classList.contains("fast-edit-input")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.prevSelection = null;
|
||||
this.isMouseDown = false;
|
||||
this.onSelectionChanged();
|
||||
}
|
||||
|
||||
@bind
|
||||
selectionchange() {
|
||||
if (!this.isMouseDown && !this.reselected) {
|
||||
this.onSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
didInsert(element) {
|
||||
this.element = element;
|
||||
|
||||
document.addEventListener("mousedown", this.mousedown);
|
||||
document.addEventListener("mouseup", this.mouseup);
|
||||
document.addEventListener("selectionchange", this.selectionchange);
|
||||
|
||||
this.appEvents.on("quote-button:quote", this, "insertQuote");
|
||||
this.appEvents.on("quote-button:edit", this, "toggleFastEditForm");
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
this.popper?.destroy();
|
||||
|
||||
document.removeEventListener("mousedown", this.mousedown);
|
||||
document.removeEventListener("mouseup", this.mouseup);
|
||||
document.removeEventListener("selectionchange", this.selectionchange);
|
||||
|
||||
this.appEvents.off("quote-button:quote", this, "insertQuote");
|
||||
this.appEvents.off("quote-button:edit", this, "toggleFastEditForm");
|
||||
this.teardownSelectionListeners();
|
||||
}
|
||||
|
||||
get post() {
|
||||
return this.args.topic.postStream.findLoadedPost(
|
||||
this.args.quoteState.postId
|
||||
);
|
||||
}
|
||||
|
||||
get quoteSharingEnabled() {
|
||||
return (
|
||||
this.site.desktopView &&
|
||||
this.quoteSharingSources.length > 0 &&
|
||||
!this.args.topic.invisible &&
|
||||
!this.args.topic.category?.read_restricted &&
|
||||
(this.siteSettings.share_quote_visibility === "all" ||
|
||||
(this.siteSettings.share_quote_visibility === "anonymous" &&
|
||||
!this.currentUser))
|
||||
);
|
||||
}
|
||||
|
||||
get quoteSharingSources() {
|
||||
return Sharing.activeSources(
|
||||
this.siteSettings.share_quote_buttons,
|
||||
this.siteSettings.login_required || isPM
|
||||
this.siteSettings.login_required || this.args.topic.isPrivateMessage
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("topic.{isPrivateMessage,invisible,category}")
|
||||
quoteSharingShowLabel() {
|
||||
get quoteSharingShowLabel() {
|
||||
return this.quoteSharingSources.length > 1;
|
||||
},
|
||||
}
|
||||
|
||||
@computed("topic.{id,slug}", "post")
|
||||
get shareUrl() {
|
||||
return getAbsoluteURL(
|
||||
postUrl(this.topic.slug, this.topic.id, this.post.post_number)
|
||||
postUrl(this.args.topic.slug, this.args.topic.id, this.post.post_number)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
get embedQuoteButton() {
|
||||
const canCreatePost = this.args.topic.details.can_create_post;
|
||||
const canReplyAsNewTopic = this.args.topic.details.can_reply_as_new_topic;
|
||||
|
||||
@discourseComputed(
|
||||
"topic.details.can_create_post",
|
||||
"topic.details.can_reply_as_new_topic"
|
||||
)
|
||||
embedQuoteButton(canCreatePost, canReplyAsNewTopic) {
|
||||
return (
|
||||
(canCreatePost || canReplyAsNewTopic) &&
|
||||
this.currentUser?.get("user_option.enable_quoting")
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
get canEditPost() {
|
||||
return this.siteSettings.enable_fast_edit && this.post?.can_edit;
|
||||
}
|
||||
|
||||
@action
|
||||
insertQuote() {
|
||||
this.attrs.selectText().then(() => this._hideButton());
|
||||
},
|
||||
async insertQuote() {
|
||||
await this.args.selectText();
|
||||
this.hideButton();
|
||||
}
|
||||
|
||||
@action
|
||||
async _toggleFastEditForm() {
|
||||
if (this._isFastEditable) {
|
||||
async toggleFastEditForm() {
|
||||
if (this.isFastEditable) {
|
||||
if (this.site.desktopView) {
|
||||
this.toggleProperty("_displayFastEditInput");
|
||||
this.displayFastEditInput = !this.displayFastEditInput;
|
||||
} else {
|
||||
this.modal.show(FastEditModal, {
|
||||
model: {
|
||||
initialValue: this._fastEditInitialSelection,
|
||||
initialValue: this.fastEditInitialSelection,
|
||||
post: this.post,
|
||||
},
|
||||
});
|
||||
this._hideButton();
|
||||
this.hideButton();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ajax(`/posts/${this.post.id}`, { cache: false });
|
||||
const result = await ajax(`/posts/${this.post.id}`);
|
||||
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let bestIndex = 0;
|
||||
const rows = result.raw.split("\n");
|
||||
|
||||
@@ -386,7 +397,7 @@ export default Component.extend({
|
||||
// "* " at the beginning of the buffer, we remove it to be able
|
||||
// to find it in row
|
||||
const buffer = fixQuotes(
|
||||
this.quoteState.buffer.split("\n")[0].replace(/^\* /, "")
|
||||
this.args.quoteState.buffer.split("\n")[0].replace(/^\* /, "")
|
||||
);
|
||||
|
||||
rows.some((row, index) => {
|
||||
@@ -396,7 +407,7 @@ export default Component.extend({
|
||||
}
|
||||
});
|
||||
|
||||
this.editPost(this.post);
|
||||
this.args.editPost(this.post);
|
||||
|
||||
document
|
||||
.querySelector("#reply-control")
|
||||
@@ -418,14 +429,14 @@ export default Component.extend({
|
||||
textarea.blur();
|
||||
textarea.focus();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
share(source) {
|
||||
Sharing.shareSource(source, {
|
||||
url: this.shareUrl,
|
||||
title: this.topic.title,
|
||||
title: this.args.topic.title,
|
||||
quote: window.getSelection().toString(),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ export default {
|
||||
},
|
||||
|
||||
quoteReply() {
|
||||
if (this.isPostTextSelected()) {
|
||||
if (this.isPostTextSelected) {
|
||||
this.appEvents.trigger("quote-button:quote");
|
||||
return false;
|
||||
}
|
||||
@@ -330,7 +330,7 @@ export default {
|
||||
},
|
||||
|
||||
editPost() {
|
||||
if (this.siteSettings.enable_fast_edit && this.isPostTextSelected()) {
|
||||
if (this.siteSettings.enable_fast_edit && this.isPostTextSelected) {
|
||||
this.appEvents.trigger("quote-button:edit");
|
||||
return false;
|
||||
} else {
|
||||
@@ -556,9 +556,9 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
isPostTextSelected() {
|
||||
get isPostTextSelected() {
|
||||
const topicController = getOwner(this).lookup("controller:topic");
|
||||
return !!topicController?.get("quoteState")?.postId;
|
||||
return !!topicController.quoteState.postId;
|
||||
},
|
||||
|
||||
sendToSelectedPost(action, elem) {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
export default class QuoteState {
|
||||
@tracked postId;
|
||||
@tracked buffer;
|
||||
@tracked opts;
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
}
|
||||
@tracked postId = null;
|
||||
@tracked buffer = "";
|
||||
@tracked opts = null;
|
||||
|
||||
selected(postId, buffer, opts) {
|
||||
this.postId = postId;
|
||||
|
||||
@@ -162,7 +162,7 @@ export function selectedText() {
|
||||
return toMarkdown($div.html());
|
||||
}
|
||||
|
||||
export function selectedElement() {
|
||||
export function selectedNode() {
|
||||
return selectedRange()?.commonAncestorContainer;
|
||||
}
|
||||
|
||||
|
||||
@@ -595,6 +595,5 @@
|
||||
@selectText={{action "selectText"}}
|
||||
@editPost={{action "editPost"}}
|
||||
@topic={{this.model}}
|
||||
@composerVisible={{this.composer.visible}}
|
||||
/>
|
||||
</DiscourseTopic>
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
acceptance,
|
||||
chromeTest,
|
||||
exists,
|
||||
query,
|
||||
queryAll,
|
||||
@@ -12,7 +11,6 @@ import { cloneJSON } from "discourse-common/lib/object";
|
||||
import topicFixtures from "discourse/tests/fixtures/topic";
|
||||
import { test } from "qunit";
|
||||
|
||||
// This tests are flaky on Firefox. Fails with `calling set on destroyed object`
|
||||
acceptance("Topic - Quote button - logged in", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
@@ -33,50 +31,41 @@ acceptance("Topic - Quote button - logged in", function (needs) {
|
||||
);
|
||||
});
|
||||
|
||||
chromeTest(
|
||||
"Does not show the quote share buttons by default",
|
||||
async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
assert.ok(exists(".insert-quote"), "it shows the quote button");
|
||||
assert.ok(!exists(".quote-sharing"), "it does not show quote sharing");
|
||||
}
|
||||
);
|
||||
test("Does not show the quote share buttons by default", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
assert.ok(exists(".insert-quote"), "it shows the quote button");
|
||||
assert.ok(!exists(".quote-sharing"), "it does not show quote sharing");
|
||||
});
|
||||
|
||||
chromeTest(
|
||||
"Shows quote share buttons with the right site settings",
|
||||
async function (assert) {
|
||||
this.siteSettings.share_quote_visibility = "all";
|
||||
test("Shows quote share buttons with the right site settings", async function (assert) {
|
||||
this.siteSettings.share_quote_visibility = "all";
|
||||
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
|
||||
assert.ok(exists(".quote-sharing"), "it shows the quote sharing options");
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`),
|
||||
"it includes the twitter share button"
|
||||
);
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.email")}']`),
|
||||
"it includes the email share button"
|
||||
);
|
||||
}
|
||||
);
|
||||
assert.ok(exists(".quote-sharing"), "it shows the quote sharing options");
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`),
|
||||
"it includes the twitter share button"
|
||||
);
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.email")}']`),
|
||||
"it includes the email share button"
|
||||
);
|
||||
});
|
||||
|
||||
chromeTest(
|
||||
"Quoting a Onebox should not copy the formatting of the rendered Onebox",
|
||||
async function (assert) {
|
||||
await visit("/t/topic-for-group-moderators/2480");
|
||||
await selectText("#post_3 aside.onebox p");
|
||||
await click(".insert-quote");
|
||||
test("Quoting a Onebox should not copy the formatting of the rendered Onebox", async function (assert) {
|
||||
await visit("/t/topic-for-group-moderators/2480");
|
||||
await selectText("#post_3 aside.onebox p");
|
||||
await click(".insert-quote");
|
||||
|
||||
assert.strictEqual(
|
||||
query(".d-editor-input").value.trim(),
|
||||
'[quote="group_moderator, post:3, topic:2480"]\nhttps://example.com/57350945\n[/quote]',
|
||||
"quote only contains a link"
|
||||
);
|
||||
}
|
||||
);
|
||||
assert.strictEqual(
|
||||
query(".d-editor-input").value.trim(),
|
||||
'[quote="group_moderator, post:3, topic:2480"]\nhttps://example.com/57350945\n[/quote]',
|
||||
"quote only contains a link"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Closed Topic - Quote button - logged in", function (needs) {
|
||||
@@ -90,7 +79,7 @@ acceptance("Closed Topic - Quote button - logged in", function (needs) {
|
||||
server.get("/t/280.json", () => helper.response(topicResponse));
|
||||
});
|
||||
|
||||
chromeTest("Shows quote button in closed topics", async function (assert) {
|
||||
test("Shows quote button in closed topics", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_1 .cooked p:first-child");
|
||||
assert.ok(exists(".insert-quote"), "it shows the quote button");
|
||||
@@ -111,60 +100,48 @@ acceptance("Topic - Quote button - anonymous", function (needs) {
|
||||
share_quote_buttons: "twitter|email",
|
||||
});
|
||||
|
||||
chromeTest(
|
||||
"Shows quote share buttons with the right site settings",
|
||||
async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
test("Shows quote share buttons with the right site settings", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
|
||||
assert.ok(
|
||||
queryAll(".quote-sharing"),
|
||||
"it shows the quote sharing options"
|
||||
);
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`),
|
||||
"it includes the twitter share button"
|
||||
);
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.email")}']`),
|
||||
"it includes the email share button"
|
||||
);
|
||||
assert.ok(!exists(".insert-quote"), "it does not show the quote button");
|
||||
}
|
||||
);
|
||||
assert.ok(queryAll(".quote-sharing"), "it shows the quote sharing options");
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`),
|
||||
"it includes the twitter share button"
|
||||
);
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.email")}']`),
|
||||
"it includes the email share button"
|
||||
);
|
||||
assert.ok(!exists(".insert-quote"), "it does not show the quote button");
|
||||
});
|
||||
|
||||
chromeTest(
|
||||
"Shows single share button when site setting only has one item",
|
||||
async function (assert) {
|
||||
this.siteSettings.share_quote_buttons = "twitter";
|
||||
test("Shows single share button when site setting only has one item", async function (assert) {
|
||||
this.siteSettings.share_quote_buttons = "twitter";
|
||||
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
|
||||
assert.ok(exists(".quote-sharing"), "it shows the quote sharing options");
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`),
|
||||
"it includes the twitter share button"
|
||||
);
|
||||
assert.ok(
|
||||
!exists(".quote-share-label"),
|
||||
"it does not show the Share label"
|
||||
);
|
||||
}
|
||||
);
|
||||
assert.ok(exists(".quote-sharing"), "it shows the quote sharing options");
|
||||
assert.ok(
|
||||
exists(`.quote-sharing .btn[title='${I18n.t("share.twitter")}']`),
|
||||
"it includes the twitter share button"
|
||||
);
|
||||
assert.ok(
|
||||
!exists(".quote-share-label"),
|
||||
"it does not show the Share label"
|
||||
);
|
||||
});
|
||||
|
||||
chromeTest(
|
||||
"Shows nothing when visibility is disabled",
|
||||
async function (assert) {
|
||||
this.siteSettings.share_quote_visibility = "none";
|
||||
test("Shows nothing when visibility is disabled", async function (assert) {
|
||||
this.siteSettings.share_quote_visibility = "none";
|
||||
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await selectText("#post_5 blockquote");
|
||||
|
||||
assert.ok(!exists(".quote-sharing"), "it does not show quote sharing");
|
||||
assert.ok(!exists(".insert-quote"), "it does not show the quote button");
|
||||
}
|
||||
);
|
||||
assert.ok(!exists(".quote-sharing"), "it does not show quote sharing");
|
||||
assert.ok(!exists(".insert-quote"), "it does not show the quote button");
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Topic - Quote button - keyboard shortcut", function (needs) {
|
||||
|
||||
Reference in New Issue
Block a user