mirror of
https://github.com/discourse/discourse.git
synced 2025-05-23 07:01:13 +08:00
FIX: Make sure user preference to open external links in new tab works for bookmark list excerpts (#10409)
Meta post: https://meta.discourse.org/t/bookmark-page-does-not-respect-open-all-external-links-in-new-tab-user-preference/160118
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import { schedule } from "@ember/runloop";
|
||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
@ -6,6 +7,10 @@ import { inject } from "@ember/controller";
|
|||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import Bookmark from "discourse/models/bookmark";
|
import Bookmark from "discourse/models/bookmark";
|
||||||
|
import {
|
||||||
|
shouldOpenInNewTab,
|
||||||
|
openLinkInNewTab
|
||||||
|
} from "discourse/lib/click-track";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
application: inject(),
|
application: inject(),
|
||||||
@ -19,6 +24,11 @@ export default Controller.extend({
|
|||||||
|
|
||||||
queryParams: ["q"],
|
queryParams: ["q"],
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this._boundClick = false;
|
||||||
|
},
|
||||||
|
|
||||||
loadItems() {
|
loadItems() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
content: [],
|
content: [],
|
||||||
@ -30,16 +40,36 @@ export default Controller.extend({
|
|||||||
this.set("searchTerm", this.q);
|
this.set("searchTerm", this.q);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._boundClick) {
|
||||||
|
schedule("afterRender", () => {
|
||||||
|
// TODO(martin): This should be pulled out into a bookmark-list component,
|
||||||
|
// the controller is not the best place for this.
|
||||||
|
let wrapper = document.querySelector(".bookmark-list-wrapper");
|
||||||
|
if (!wrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrapper.addEventListener("click", function(e) {
|
||||||
|
if (e.target && e.target.tagName === "A") {
|
||||||
|
let link = e.target;
|
||||||
|
if (shouldOpenInNewTab(link.href)) {
|
||||||
|
openLinkInNewTab(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._boundClick = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this.model
|
return this.model
|
||||||
.loadItems({ q: this.searchTerm })
|
.loadItems({ q: this.searchTerm })
|
||||||
.then(response => this._processLoadResponse(response))
|
.then(response => this._processLoadResponse(response))
|
||||||
.catch(() => this._bookmarksListDenied())
|
.catch(() => this._bookmarksListDenied())
|
||||||
.finally(() =>
|
.finally(() => {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
loaded: true,
|
loaded: true,
|
||||||
loading: false
|
loading: false
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("loaded", "content.length", "noResultsHelp")
|
@discourseComputed("loaded", "content.length", "noResultsHelp")
|
||||||
|
@ -34,6 +34,41 @@ export function isValidLink($link) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldOpenInNewTab(href) {
|
||||||
|
const isInternal = DiscourseURL.isInternal(href);
|
||||||
|
const openExternalInNewTab = User.currentProp("external_links_in_new_tab");
|
||||||
|
return !isInternal && openExternalInNewTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openLinkInNewTab(link) {
|
||||||
|
let href = (link.href || link.dataset.href || "").trim();
|
||||||
|
if (href === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newWindow = window.open(href, "_blank");
|
||||||
|
newWindow.opener = null;
|
||||||
|
newWindow.focus();
|
||||||
|
|
||||||
|
// Hack to prevent changing current window.location.
|
||||||
|
// e.preventDefault() does not work.
|
||||||
|
if (!link.dataset.href) {
|
||||||
|
link.classList.add("no-href");
|
||||||
|
link.dataset.href = link.href;
|
||||||
|
link.dataset.autoRoute = true;
|
||||||
|
link.removeAttribute("href");
|
||||||
|
|
||||||
|
later(() => {
|
||||||
|
if (link) {
|
||||||
|
link.classList.remove("no-href");
|
||||||
|
link.setAttribute("href", link.dataset.href);
|
||||||
|
delete link.dataset.href;
|
||||||
|
delete link.dataset.autoRoute;
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
trackClick(e, siteSettings) {
|
trackClick(e, siteSettings) {
|
||||||
// right clicks are not tracked
|
// right clicks are not tracked
|
||||||
@ -121,33 +156,12 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInternal = DiscourseURL.isInternal(href);
|
|
||||||
const openExternalInNewTab = User.currentProp("external_links_in_new_tab");
|
|
||||||
|
|
||||||
if (!wantsNewWindow(e)) {
|
if (!wantsNewWindow(e)) {
|
||||||
if (!isInternal && openExternalInNewTab) {
|
if (shouldOpenInNewTab(href)) {
|
||||||
const newWindow = window.open(href, "_blank");
|
openLinkInNewTab($link[0]);
|
||||||
newWindow.opener = null;
|
|
||||||
newWindow.focus();
|
|
||||||
|
|
||||||
// Hack to prevent changing current window.location.
|
|
||||||
// e.preventDefault() does not work.
|
|
||||||
if (!$link.data("href")) {
|
|
||||||
$link.addClass("no-href");
|
|
||||||
$link.data("href", $link.attr("href"));
|
|
||||||
$link.attr("href", null);
|
|
||||||
$link.data("auto-route", true);
|
|
||||||
|
|
||||||
later(() => {
|
|
||||||
$link.removeClass("no-href");
|
|
||||||
$link.attr("href", $link.data("href"));
|
|
||||||
$link.data("href", null);
|
|
||||||
$link.data("auto-route", null);
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
trackPromise.finally(() => {
|
trackPromise.finally(() => {
|
||||||
if (isInternal) {
|
if (DiscourseURL.isInternal(href)) {
|
||||||
DiscourseURL.routeTo(href);
|
DiscourseURL.routeTo(href);
|
||||||
} else {
|
} else {
|
||||||
DiscourseURL.redirectTo(href);
|
DiscourseURL.redirectTo(href);
|
||||||
|
@ -13,83 +13,85 @@
|
|||||||
{{#if noContent}}
|
{{#if noContent}}
|
||||||
<div class="alert alert-info">{{noResultsHelp}}</div>
|
<div class="alert alert-info">{{noResultsHelp}}</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#conditional-loading-spinner condition=loading}}
|
<div class="bookmark-list-wrapper">
|
||||||
{{#load-more selector=".bookmark-list tr" action=(action "loadMore")}}
|
{{#conditional-loading-spinner condition=loading}}
|
||||||
<table class="topic-list bookmark-list">
|
{{#load-more selector=".bookmark-list .bookmark-list-item" action=(action "loadMore")}}
|
||||||
<thead>
|
<table class="topic-list bookmark-list">
|
||||||
{{#if site.mobileView}}
|
<thead>
|
||||||
<th> </th>
|
{{#if site.mobileView}}
|
||||||
<th>{{i18n "topic.title"}}</th>
|
<th> </th>
|
||||||
<th> </th>
|
<th>{{i18n "topic.title"}}</th>
|
||||||
{{else}}
|
<th> </th>
|
||||||
<th>{{i18n "topic.title"}}</th>
|
{{else}}
|
||||||
<th> </th>
|
<th>{{i18n "topic.title"}}</th>
|
||||||
<th class="post-metadata">{{i18n "post.bookmarks.updated"}}</th>
|
<th> </th>
|
||||||
<th class="post-metadata">{{i18n "activity"}}</th>
|
<th class="post-metadata">{{i18n "post.bookmarks.updated"}}</th>
|
||||||
<th> </th>
|
<th class="post-metadata">{{i18n "activity"}}</th>
|
||||||
{{/if}}
|
<th> </th>
|
||||||
</thead>
|
{{/if}}
|
||||||
<tbody>
|
</thead>
|
||||||
{{#each content as |bookmark|}}
|
<tbody>
|
||||||
<tr class="topic-list-item bookmark-list-item">
|
{{#each content as |bookmark|}}
|
||||||
{{#if site.mobileView}}
|
<tr class="topic-list-item bookmark-list-item">
|
||||||
<td>
|
{{#if site.mobileView}}
|
||||||
{{#if bookmark.post_user_avatar_template}}
|
<td>
|
||||||
<a href={{bookmark.postUser.path}} data-user-card={{bookmark.post_user_username}}>
|
{{#if bookmark.post_user_avatar_template}}
|
||||||
{{avatar bookmark.postUser avatarTemplatePath="avatar_template" usernamePath="username" namePath="name" imageSize="small"}}
|
<a href={{bookmark.postUser.path}} data-user-card={{bookmark.post_user_username}}>
|
||||||
</a>
|
{{avatar bookmark.postUser avatarTemplatePath="avatar_template" usernamePath="username" namePath="name" imageSize="small"}}
|
||||||
{{/if}}
|
</a>
|
||||||
</td>
|
|
||||||
{{/if}}
|
|
||||||
<td class="main-link">
|
|
||||||
<span class="link-top-line">
|
|
||||||
<div class="bookmark-metadata">
|
|
||||||
{{#if bookmark.name}}
|
|
||||||
<span class="bookmark-metadata-item">
|
|
||||||
{{d-icon "info-circle"}}{{bookmark.name}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if bookmark.reminder_at}}
|
</td>
|
||||||
<span class="bookmark-metadata-item">
|
|
||||||
{{d-icon "far-clock"}}{{bookmark.formattedReminder}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{topic-status topic=bookmark}}
|
|
||||||
{{topic-link bookmark}}
|
|
||||||
</span>
|
|
||||||
{{#if bookmark.excerpt}}
|
|
||||||
<p class="post-excerpt">{{html-safe bookmark.excerpt}}</p>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="link-bottom-line">
|
<td class="main-link">
|
||||||
{{category-link bookmark.category}}
|
<span class="link-top-line">
|
||||||
{{discourse-tags bookmark mode="list" tagsForUser=tagsForUser}}
|
<div class="bookmark-metadata">
|
||||||
</div>
|
{{#if bookmark.name}}
|
||||||
</td>
|
<span class="bookmark-metadata-item">
|
||||||
{{#unless site.mobileView}}
|
{{d-icon "info-circle"}}{{bookmark.name}}
|
||||||
<td>
|
</span>
|
||||||
{{#if bookmark.post_user_avatar_template}}
|
{{/if}}
|
||||||
<a href={{bookmark.postUser.path}} data-user-card={{bookmark.post_user_username}}>
|
{{#if bookmark.reminder_at}}
|
||||||
{{avatar bookmark.postUser avatarTemplatePath="avatar_template" usernamePath="username" namePath="name" imageSize="small"}}
|
<span class="bookmark-metadata-item">
|
||||||
</a>
|
{{d-icon "far-clock"}}{{bookmark.formattedReminder}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{topic-status topic=bookmark}}
|
||||||
|
{{topic-link bookmark}}
|
||||||
|
</span>
|
||||||
|
{{#if bookmark.excerpt}}
|
||||||
|
<p class="post-excerpt">{{html-safe bookmark.excerpt}}</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<div class="link-bottom-line">
|
||||||
|
{{category-link bookmark.category}}
|
||||||
|
{{discourse-tags bookmark mode="list" tagsForUser=tagsForUser}}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="post-metadata">{{format-date bookmark.updated_at format="tiny"}}</td>
|
{{#unless site.mobileView}}
|
||||||
{{raw "list/activity-column" topic=bookmark class="num post-metadata" tagName="td"}}
|
<td>
|
||||||
{{/unless}}
|
{{#if bookmark.post_user_avatar_template}}
|
||||||
<td>
|
<a href={{bookmark.postUser.path}} data-user-card={{bookmark.post_user_username}}>
|
||||||
{{bookmark-actions-dropdown
|
{{avatar bookmark.postUser avatarTemplatePath="avatar_template" usernamePath="username" namePath="name" imageSize="small"}}
|
||||||
bookmark=bookmark
|
</a>
|
||||||
removeBookmark=(action "removeBookmark")
|
{{/if}}
|
||||||
editBookmark=(action "editBookmark")
|
</td>
|
||||||
}}
|
<td class="post-metadata">{{format-date bookmark.updated_at format="tiny"}}</td>
|
||||||
</td>
|
{{raw "list/activity-column" topic=bookmark class="num post-metadata" tagName="td"}}
|
||||||
</tr>
|
{{/unless}}
|
||||||
{{/each}}
|
<td>
|
||||||
</tbody>
|
{{bookmark-actions-dropdown
|
||||||
</table>
|
bookmark=bookmark
|
||||||
{{conditional-loading-spinner condition=loadingMore}}
|
removeBookmark=(action "removeBookmark")
|
||||||
{{/load-more}}
|
editBookmark=(action "editBookmark")
|
||||||
{{/conditional-loading-spinner}}
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{conditional-loading-spinner condition=loadingMore}}
|
||||||
|
{{/load-more}}
|
||||||
|
{{/conditional-loading-spinner}}
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -140,7 +140,7 @@ QUnit.test("does not track right clicks inside quotes", async assert => {
|
|||||||
QUnit.test("does not track clicks links in quotes", async assert => {
|
QUnit.test("does not track clicks links in quotes", async assert => {
|
||||||
User.currentProp("external_links_in_new_tab", true);
|
User.currentProp("external_links_in_new_tab", true);
|
||||||
assert.notOk(track(generateClickEventOn(".quote a:last-child")));
|
assert.notOk(track(generateClickEventOn(".quote a:last-child")));
|
||||||
assert.ok(window.open.calledWith("https://google.com", "_blank"));
|
assert.ok(window.open.calledWith("https://google.com/", "_blank"));
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("does not track clicks on category badges", async assert => {
|
QUnit.test("does not track clicks on category badges", async assert => {
|
||||||
@ -158,10 +158,10 @@ QUnit.test("removes the href and put it as a data attribute", async assert => {
|
|||||||
|
|
||||||
var $link = fixture("a").first();
|
var $link = fixture("a").first();
|
||||||
assert.ok($link.hasClass("no-href"));
|
assert.ok($link.hasClass("no-href"));
|
||||||
assert.equal($link.data("href"), "http://www.google.com");
|
assert.equal($link.data("href"), "http://www.google.com/");
|
||||||
assert.blank($link.attr("href"));
|
assert.blank($link.attr("href"));
|
||||||
assert.ok($link.data("auto-route"));
|
assert.ok($link.data("auto-route"));
|
||||||
assert.ok(window.open.calledWith("http://www.google.com", "_blank"));
|
assert.ok(window.open.calledWith("http://www.google.com/", "_blank"));
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("restores the href after a while", async assert => {
|
QUnit.test("restores the href after a while", async assert => {
|
||||||
|
Reference in New Issue
Block a user