FEATURE: Replace share-popup with share-topic (#16108)

share-topic modal is used everywhere expect when clicking on the top
right corner of the post. This changes standardize on share-topic modal
and add the missing features from share-popup.
This commit is contained in:
Bianca Nenciu 2022-03-15 21:27:18 +02:00 committed by GitHub
parent d19b5fe80b
commit 08a1f41582
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 74 additions and 341 deletions

View File

@ -1,219 +0,0 @@
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import { later, scheduleOnce } from "@ember/runloop";
import Component from "@ember/component";
import I18n from "I18n";
import Sharing from "discourse/lib/sharing";
import { alias } from "@ember/object/computed";
import { isEmpty } from "@ember/utils";
import { longDateNoYear } from "discourse/lib/formatter";
import { nativeShare } from "discourse/lib/pwa-utils";
import { wantsNewWindow } from "discourse/lib/intercept-click";
export default Component.extend({
elementId: "share-link",
classNameBindings: ["visible"],
link: null,
visible: null,
privateCategory: alias("topic.category.read_restricted"),
@discourseComputed("topic.{isPrivateMessage,invisible,category}")
sources(topic) {
const privateContext =
this.siteSettings.login_required ||
(topic && topic.isPrivateMessage) ||
(topic && topic.invisible) ||
this.privateCategory;
return Sharing.activeSources(this.siteSettings.share_links, privateContext);
},
@discourseComputed("type", "postNumber")
shareTitle(type, postNumber) {
if (type === "topic") {
return I18n.t("share.topic");
}
if (postNumber) {
return I18n.t("share.post", { postNumber });
}
return I18n.t("share.topic");
},
@discourseComputed("date")
displayDate(date) {
return longDateNoYear(new Date(date));
},
_focusUrl() {
// Wait for the fade-in transition to finish before selecting the link:
later(() => {
if (this.element) {
const linkInput = this.element.querySelector("#share-link input");
linkInput.value = this.link;
if (!this.site.mobileView) {
// if the input is auto-focused on mobile, iOS requires two taps of the copy button
linkInput.setSelectionRange(0, this.link.length);
linkInput.focus();
}
}
}, 200);
},
_showUrl($target, url) {
const currentTargetOffset = $target.offset();
const $this = $(this.element);
if (isEmpty(url)) {
return;
}
// Relative urls
if (url.indexOf("/") === 0) {
url = window.location.protocol + "//" + window.location.host + url;
}
const shareLinkWidth = $this.width();
let x = currentTargetOffset.left - shareLinkWidth / 2;
if (x < 25) {
x = 25;
}
if (x + shareLinkWidth > $(window).width()) {
x -= shareLinkWidth / 2;
}
const header = $(".d-header");
let y = currentTargetOffset.top - ($this.height() + 20);
if (y < header.offset().top + header.height()) {
y = currentTargetOffset.top + 10;
}
this.element.style.top = `${y}px`;
if (!this.site.mobileView) {
this.element.style.left = `${x}px`;
if (document.documentElement.classList.contains("rtl")) {
this.element.style.right = "unset";
}
}
this.set("link", url);
this.set("visible", true);
scheduleOnce("afterRender", this, this._focusUrl);
},
@bind
_mouseDownHandler(event) {
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
// Use mousedown instead of click so this event is handled before routing occurs when a
// link is clicked (which is a click event) while the share dialog is showing.
if ($(this.element).has(event.target).length !== 0) {
return;
}
this.send("close");
return true;
},
@bind
_clickHandler(event) {
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
// if they want to open in a new tab, let it so
if (wantsNewWindow(event)) {
return true;
}
event.preventDefault();
const $currentTarget = $(event.currentTarget);
const url = $currentTarget.data("share-url");
const postNumber = $currentTarget.data("post-number");
const postId = $currentTarget.closest("article").data("post-id");
const date = $currentTarget.children().data("time");
this.setProperties({ postNumber, date, postId });
// use native webshare only when the user clicks on the "chain" icon
if (!$currentTarget.hasClass("post-date")) {
nativeShare(this.capabilities, { url }).then(null, () =>
this._showUrl($currentTarget, url)
);
} else {
this._showUrl($currentTarget, url);
}
return false;
},
@bind
_keydownHandler(event) {
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
if (event.key === "Escape") {
this.send("close");
}
},
_shareUrlHandler(url, $target) {
this._showUrl($target, url);
},
didInsertElement() {
this._super(...arguments);
$("html")
.on("mousedown.outside-share-link", this._mouseDownHandler)
.on(
"click.discourse-share-link",
"button[data-share-url], .post-info .post-date[data-share-url]",
this._clickHandler
)
.on("keydown.share-view", this._keydownHandler);
this.appEvents.on("share:url", this, "_shareUrlHandler");
},
willDestroyElement() {
this._super(...arguments);
$("html")
.off("click.discourse-share-link", this._clickHandler)
.off("mousedown.outside-share-link", this._mouseDownHandler)
.off("keydown.share-view", this._keydownHandler);
this.appEvents.off("share:url", this, "_shareUrlHandler");
},
actions: {
replyAsNewTopic() {
const postStream = this.get("topic.postStream");
const postId = this.postId || postStream.findPostIdForPostNumber(1);
const post = postStream.findLoadedPost(postId);
this.replyAsNewTopic(post);
this.send("close");
},
close() {
this.setProperties({
link: null,
postNumber: null,
postId: null,
visible: false,
});
},
share(source) {
Sharing.shareSource(source, {
url: this.link,
title: this.get("topic.title"),
});
},
},
});

View File

@ -2,6 +2,7 @@ import Controller from "@ember/controller";
import { action } from "@ember/object";
import { getAbsoluteURL } from "discourse-common/lib/get-url";
import discourseComputed from "discourse-common/utils/decorators";
import { longDateNoYear } from "discourse/lib/formatter";
import Sharing from "discourse/lib/sharing";
import showModal from "discourse/lib/show-modal";
import { bufferedProperty } from "discourse/mixins/buffered-content";
@ -9,6 +10,7 @@ import ModalFunctionality from "discourse/mixins/modal-functionality";
import I18n from "I18n";
import Category from "discourse/models/category";
import { scheduleOnce } from "@ember/runloop";
import { getOwner } from "discourse-common/lib/get-owner";
export default Controller.extend(
ModalFunctionality,
@ -51,6 +53,12 @@ export default Controller.extend(
}
},
@discourseComputed("post.created_at", "post.wiki", "post.last_wiki_edit")
displayDate(createdAt, wiki, lastWikiEdit) {
const date = wiki && lastWikiEdit ? lastWikiEdit : createdAt;
return longDateNoYear(new Date(date));
},
@discourseComputed(
"topic.{isPrivateMessage,invisible,category.read_restricted}"
)
@ -88,6 +96,16 @@ export default Controller.extend(
});
},
@action
replyAsNewTopic() {
const postStream = this.topic.postStream;
const postId = this.post?.id || postStream.findPostIdForPostNumber(1);
const post = postStream.findLoadedPost(postId);
const topicController = getOwner(this).lookup("controller:topic");
topicController.actions.replyAsNewTopic.call(topicController, post);
this.send("closeModal");
},
restrictedGroupWarning() {
this.appEvents.on("modal:body-shown", () => {
let restrictedGroups;

View File

@ -1,52 +0,0 @@
<div class="title">
<h3>{{html-safe shareTitle}}</h3>
{{#if date}}
<span class="date">{{displayDate}}</span>
{{/if}}
{{d-button
action=(action "close")
class="btn btn-flat close"
icon="times"
aria-label="share.close"
title="share.close"
}}
</div>
<div class="link-share-container">
<input class="share-link-input" type="text" aria-label={{i18n "share.url"}}> {{copy-button selector="input.share-link-input"}}
</div>
<div class="link-share-actions">
<div class="sources">
{{#each sources as |s|}}
{{share-source source=s title=model.title action=(action "share")}}
{{/each}}
</div>
<div class="alt-actions">
{{#if topic.details.can_reply_as_new_topic}}
{{#if topic.isPrivateMessage}}
{{d-button
action=(action "replyAsNewTopic")
class="btn btn-default new-topic"
icon="plus"
aria-label="post.reply_as_new_private_message"
title="post.reply_as_new_private_message"
label="user.new_private_message"
}}
{{else}}
{{d-button
action=(action "replyAsNewTopic")
class="btn btn-default new-topic"
icon="plus"
aria-label="post.reply_as_new_topic"
title="post.reply_as_new_topic"
label="topic.create"
}}
{{/if}}
{{/if}}
</div>
</div>

View File

@ -29,7 +29,33 @@
action=(action "inviteUsers")
}}
{{/if}}
{{#if topic.details.can_reply_as_new_topic}}
{{#if topic.isPrivateMessage}}
{{d-button
action=(action "replyAsNewTopic")
class="btn-default new-topic"
icon="plus"
aria-label="post.reply_as_new_private_message"
title="post.reply_as_new_private_message"
label="user.new_private_message"
}}
{{else}}
{{d-button
action=(action "replyAsNewTopic")
class="btn-default new-topic"
icon="plus"
aria-label="post.reply_as_new_topic"
title="post.reply_as_new_topic"
label="topic.create"
}}
{{/if}}
{{/if}}
</div>
{{#if post}}
<div class="date">{{displayDate}}</div>
{{/if}}
</div>
</form>
{{/d-modal-body}}

View File

@ -406,8 +406,6 @@
</div>
{{/if}}
{{share-popup topic=model replyAsNewTopic=(action "replyAsNewTopic")}}
{{quote-button
quoteState=quoteState
selectText=(action "selectText")

View File

@ -280,21 +280,6 @@ createWidget("post-meta-data", {
);
}
const lastWikiEdit =
attrs.wiki && attrs.lastWikiEdit && new Date(attrs.lastWikiEdit);
const createdAt = new Date(attrs.created_at);
const date = lastWikiEdit ? dateNode(lastWikiEdit) : dateNode(createdAt);
const attributes = {
class: "post-date",
href: attrs.shareUrl,
"data-share-url": attrs.shareUrl,
"data-post-number": attrs.post_number,
};
if (lastWikiEdit) {
attributes["class"] += " last-wiki-edit";
}
if (attrs.via_email) {
postInfo.push(this.attach("post-email-indicator", attrs));
}
@ -315,7 +300,7 @@ createWidget("post-meta-data", {
postInfo.push(this.attach("reply-to-tab", attrs));
}
postInfo.push(h("div.post-info.post-date", h("a", { attributes }, date)));
postInfo.push(this.attach("post-date", attrs));
postInfo.push(
h(
@ -352,6 +337,29 @@ createWidget("expand-hidden", {
},
});
createWidget("post-date", {
tagName: "div.post-info.post-date",
html(attrs) {
const attributes = { class: "post-date" };
let date;
if (attrs.wiki && attrs.lastWikiEdit) {
attributes["class"] += " last-wiki-edit";
date = new Date(attrs.lastWikiEdit);
} else {
date = new Date(attrs.created_at);
}
return h("a", { attributes }, dateNode(date));
},
click() {
const post = this.findAncestorModel();
const topic = post.topic;
const controller = showModal("share-topic", { model: topic.category });
controller.setProperties({ topic, post });
},
});
createWidget("expand-post-button", {
tagName: "button.btn.expand-post",
buildKey: (attrs) => `expand-post-button-${attrs.id}`,

View File

@ -39,7 +39,7 @@ acceptance("Share and Invite modal", function (needs) {
await visit("/t/short-topic-with-two-posts/54077");
await click("#post_2 .post-info.post-date a");
assert.ok(exists("#share-link"), "it shows the share modal");
assert.ok(exists(".share-topic-modal"), "it shows the share modal");
});
test("Share topic in a restricted category", async function (assert) {

View File

@ -74,7 +74,7 @@ acceptance("Topic", function (needs) {
await visit("/t/internationalization-localization/280");
await click(".topic-post:first-child button.share");
assert.ok(exists("#share-link"), "it shows the share modal");
assert.ok(exists(".share-topic-modal"), "it shows the share modal");
});
test("Showing and hiding the edit controls", async function (assert) {

View File

@ -22,10 +22,7 @@ discourseModule("Integration | Component | Widget | post", function (hooks) {
},
test(assert) {
assert.ok(exists(".names"), "includes poster name");
assert.ok(exists("a.post-date"), "includes post date");
assert.ok(exists("a.post-date[data-share-url]"));
assert.ok(exists("a.post-date[data-post-number]"));
},
});

View File

@ -17,7 +17,7 @@
.link-share-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
align-items: baseline;
button {
margin-top: 0.5em;
@ -26,17 +26,15 @@
.sources {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.new-topic {
margin-right: 0;
}
.alt-actions {
display: flex;
justify-content: flex-end;
.date {
color: var(--primary-med-or-secondary-med);
font-weight: normal;
margin-left: auto;
}
}
@ -52,47 +50,6 @@
}
}
// post share popup
#share-link {
position: absolute;
z-index: z("dropdown");
box-shadow: shadow("card");
background-color: var(--secondary);
padding: 0.5em;
width: 20em; // scales with user font-size
max-width: 100vw; // prevents overflow due to extra large user font-size
display: none;
&.visible {
display: block;
}
input[type="text"] {
width: 100%;
}
.title {
margin-bottom: 0.5em;
align-items: center;
display: flex;
h3 {
font-size: $font-0;
margin: 0;
}
.date {
font-weight: normal;
color: var(--primary-med-or-secondary-med);
margin-left: 0.5em;
}
.btn.close {
margin-left: auto;
}
}
}
// topic share modal
.share-topic-modal {