This is a problem that has long plagued Discourse. The root issue here is that we have to implement our own link click handler, because Discourse allows users to create HTML blobs of content with links, and when those links are clicked they must be handled by the Ember router. This always involved a certain amount of use of private Ember APIs which of course evolved over time. The API has more or less stabilized in the last two years, however we have hacks in our URLs to handle a dynamic root path, depending on how forums have set up their filters and in what order. This patch adds a special case for the root path so we needn't update the URL ourselves otherwise, which preserves the back button on index routes. The update call would otherwise insert an extra history event if a route redirected on transition, breaking the back button.
357 lines
9.2 KiB
JavaScript
357 lines
9.2 KiB
JavaScript
import { createWidget, applyDecorators } from "discourse/widgets/widget";
|
|
import { h } from "virtual-dom";
|
|
import DiscourseURL from "discourse/lib/url";
|
|
import { ajax } from "discourse/lib/ajax";
|
|
import { userPath } from "discourse/lib/url";
|
|
import { NotificationLevels } from "discourse/lib/notification-levels";
|
|
|
|
const flatten = array => [].concat.apply([], array);
|
|
|
|
createWidget("priority-faq-link", {
|
|
tagName: "a.faq-priority.widget-link",
|
|
|
|
buildAttributes(attrs) {
|
|
return { href: attrs.href };
|
|
},
|
|
|
|
html() {
|
|
return [
|
|
I18n.t("faq"),
|
|
" ",
|
|
h("span.badge.badge-notification", I18n.t("new_item"))
|
|
];
|
|
},
|
|
|
|
click(e) {
|
|
e.preventDefault();
|
|
if (this.siteSettings.faq_url === this.attrs.href) {
|
|
ajax(userPath("read-faq"), { method: "POST" }).then(() => {
|
|
this.currentUser.set("read_faq", true);
|
|
DiscourseURL.routeToTag($(e.target).closest("a")[0]);
|
|
});
|
|
} else {
|
|
DiscourseURL.routeToTag($(e.target).closest("a")[0]);
|
|
}
|
|
}
|
|
});
|
|
|
|
export default createWidget("hamburger-menu", {
|
|
tagName: "div.hamburger-panel",
|
|
|
|
settings: {
|
|
showCategories: true,
|
|
maxWidth: 320,
|
|
showFAQ: true,
|
|
showAbout: true
|
|
},
|
|
|
|
adminLinks() {
|
|
const { currentUser } = this;
|
|
|
|
const links = [
|
|
{
|
|
route: "admin",
|
|
className: "admin-link",
|
|
icon: "wrench",
|
|
label: "admin_title"
|
|
}
|
|
];
|
|
|
|
if (currentUser.admin) {
|
|
links.push({
|
|
href: "/admin/site_settings",
|
|
icon: "cog",
|
|
label: "admin.site_settings.title",
|
|
className: "settings-link"
|
|
});
|
|
}
|
|
|
|
return links.map(l => this.attach("link", l));
|
|
},
|
|
|
|
lookupCount(type) {
|
|
const tts = this.register.lookup("topic-tracking-state:main");
|
|
return tts ? tts.lookupCount(type) : 0;
|
|
},
|
|
|
|
showUserDirectory() {
|
|
if (!this.siteSettings.enable_user_directory) return false;
|
|
if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser)
|
|
return false;
|
|
return true;
|
|
},
|
|
|
|
generalLinks() {
|
|
const { siteSettings } = this;
|
|
const links = [];
|
|
|
|
links.push({
|
|
route: "discovery.latest",
|
|
className: "latest-topics-link",
|
|
label: "filters.latest.title",
|
|
title: "filters.latest.help"
|
|
});
|
|
|
|
if (this.currentUser) {
|
|
links.push({
|
|
route: "discovery.new",
|
|
className: "new-topics-link",
|
|
labelCount: "filters.new.title_with_count",
|
|
label: "filters.new.title",
|
|
title: "filters.new.help",
|
|
count: this.lookupCount("new")
|
|
});
|
|
|
|
links.push({
|
|
route: "discovery.unread",
|
|
className: "unread-topics-link",
|
|
labelCount: "filters.unread.title_with_count",
|
|
label: "filters.unread.title",
|
|
title: "filters.unread.help",
|
|
count: this.lookupCount("unread")
|
|
});
|
|
}
|
|
|
|
// Staff always see the review link. Non-staff will see it if there are items to review
|
|
if (
|
|
this.currentUser &&
|
|
(this.currentUser.staff || this.currentUser.reviewable_count)
|
|
) {
|
|
links.push({
|
|
route: siteSettings.reviewable_default_topics
|
|
? "review.topics"
|
|
: "review",
|
|
className: "review",
|
|
label: "review.title",
|
|
badgeCount: "reviewable_count",
|
|
badgeClass: "reviewables"
|
|
});
|
|
}
|
|
|
|
links.push({
|
|
route: "discovery.top",
|
|
className: "top-topics-link",
|
|
label: "filters.top.title",
|
|
title: "filters.top.help"
|
|
});
|
|
|
|
if (siteSettings.enable_badges) {
|
|
links.push({
|
|
route: "badges",
|
|
className: "badge-link",
|
|
label: "badges.title"
|
|
});
|
|
}
|
|
|
|
if (this.showUserDirectory()) {
|
|
links.push({
|
|
route: "users",
|
|
className: "user-directory-link",
|
|
label: "directory.title"
|
|
});
|
|
}
|
|
|
|
if (this.siteSettings.enable_group_directory) {
|
|
links.push({
|
|
route: "groups",
|
|
className: "groups-link",
|
|
label: "groups.index.title"
|
|
});
|
|
}
|
|
|
|
if (this.siteSettings.tagging_enabled) {
|
|
links.push({ route: "tags", label: "tagging.tags" });
|
|
}
|
|
|
|
const extraLinks = flatten(
|
|
applyDecorators(this, "generalLinks", this.attrs, this.state)
|
|
);
|
|
return links.concat(extraLinks).map(l => this.attach("link", l));
|
|
},
|
|
|
|
listCategories() {
|
|
const maxCategoriesToDisplay = this.siteSettings
|
|
.header_dropdown_category_count;
|
|
let categories = this.site.get("categoriesByCount");
|
|
|
|
if (this.currentUser) {
|
|
const allCategories = this.site
|
|
.get("categories")
|
|
.filter(c => c.notification_level !== NotificationLevels.MUTED);
|
|
|
|
categories = allCategories
|
|
.filter(c => c.get("newTopics") > 0 || c.get("unreadTopics") > 0)
|
|
.sort((a, b) => {
|
|
return (
|
|
b.get("newTopics") +
|
|
b.get("unreadTopics") -
|
|
(a.get("newTopics") + a.get("unreadTopics"))
|
|
);
|
|
});
|
|
|
|
const topCategoryIds = this.currentUser.get("top_category_ids") || [];
|
|
topCategoryIds.forEach(id => {
|
|
const category = allCategories.find(c => c.id === id);
|
|
if (category && !categories.includes(category)) {
|
|
categories.push(category);
|
|
}
|
|
});
|
|
|
|
categories = categories.concat(
|
|
allCategories
|
|
.filter(c => !categories.includes(c))
|
|
.sort((a, b) => b.topic_count - a.topic_count)
|
|
);
|
|
}
|
|
|
|
if (!this.siteSettings.allow_uncategorized_topics) {
|
|
categories = categories.filter(
|
|
c => c.id !== this.site.uncategorized_category_id
|
|
);
|
|
}
|
|
|
|
const moreCount = categories.length - maxCategoriesToDisplay;
|
|
categories = categories.slice(0, maxCategoriesToDisplay);
|
|
|
|
return this.attach("hamburger-categories", { categories, moreCount });
|
|
},
|
|
|
|
footerLinks(prioritizeFaq, faqUrl) {
|
|
const links = [];
|
|
if (this.settings.showAbout) {
|
|
links.push({
|
|
route: "about",
|
|
className: "about-link",
|
|
label: "about.simple_title"
|
|
});
|
|
}
|
|
|
|
if (this.settings.showFAQ && !prioritizeFaq) {
|
|
links.push({ href: faqUrl, className: "faq-link", label: "faq" });
|
|
}
|
|
|
|
const { site } = this;
|
|
if (!site.mobileView && !this.capabilities.touch) {
|
|
links.push({
|
|
href: "",
|
|
action: "showKeyboard",
|
|
className: "keyboard-shortcuts-link",
|
|
label: "keyboard_shortcuts_help.title"
|
|
});
|
|
}
|
|
|
|
if (
|
|
this.site.mobileView ||
|
|
(this.siteSettings.enable_mobile_theme && this.capabilities.touch)
|
|
) {
|
|
links.push({
|
|
action: "toggleMobileView",
|
|
className: "mobile-toggle-link",
|
|
label: this.site.mobileView ? "desktop_view" : "mobile_view"
|
|
});
|
|
}
|
|
|
|
const extraLinks = flatten(
|
|
applyDecorators(this, "footerLinks", this.attrs, this.state)
|
|
);
|
|
return links.concat(extraLinks).map(l => this.attach("link", l));
|
|
},
|
|
|
|
panelContents() {
|
|
const { currentUser } = this;
|
|
const results = [];
|
|
|
|
let faqUrl = this.siteSettings.faq_url;
|
|
if (!faqUrl || faqUrl.length === 0) {
|
|
faqUrl = Discourse.getURL("/faq");
|
|
}
|
|
|
|
const prioritizeFaq =
|
|
this.settings.showFAQ && this.currentUser && !this.currentUser.read_faq;
|
|
|
|
if (prioritizeFaq) {
|
|
results.push(
|
|
this.attach("menu-links", {
|
|
name: "faq-link",
|
|
heading: true,
|
|
contents: () => {
|
|
return this.attach("priority-faq-link", { href: faqUrl });
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
if (currentUser && currentUser.staff) {
|
|
results.push(
|
|
this.attach("menu-links", {
|
|
name: "admin-links",
|
|
contents: () => {
|
|
const extraLinks = flatten(
|
|
applyDecorators(this, "admin-links", this.attrs, this.state)
|
|
);
|
|
return this.adminLinks().concat(extraLinks);
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
results.push(
|
|
this.attach("menu-links", {
|
|
name: "general-links",
|
|
contents: () => this.generalLinks()
|
|
})
|
|
);
|
|
|
|
if (this.settings.showCategories) {
|
|
results.push(this.listCategories());
|
|
results.push(h("hr"));
|
|
}
|
|
|
|
results.push(
|
|
this.attach("menu-links", {
|
|
name: "footer-links",
|
|
omitRule: true,
|
|
contents: () => this.footerLinks(prioritizeFaq, faqUrl)
|
|
})
|
|
);
|
|
|
|
return results;
|
|
},
|
|
|
|
html() {
|
|
return this.attach("menu-panel", {
|
|
contents: () => this.panelContents(),
|
|
maxWidth: this.settings.maxWidth
|
|
});
|
|
},
|
|
|
|
clickOutsideMobile(e) {
|
|
const $centeredElement = $(document.elementFromPoint(e.clientX, e.clientY));
|
|
if (
|
|
$centeredElement.parents(".panel").length &&
|
|
!$centeredElement.hasClass("header-cloak")
|
|
) {
|
|
this.sendWidgetAction("toggleHamburger");
|
|
} else {
|
|
const $window = $(window);
|
|
const windowWidth = parseInt($window.width(), 10);
|
|
const $panel = $(".menu-panel");
|
|
$panel.addClass("animate");
|
|
const panelOffsetDirection = this.site.mobileView ? "left" : "right";
|
|
$panel.css(panelOffsetDirection, -windowWidth);
|
|
const $headerCloak = $(".header-cloak");
|
|
$headerCloak.addClass("animate");
|
|
$headerCloak.css("opacity", 0);
|
|
Ember.run.later(() => this.sendWidgetAction("toggleHamburger"), 200);
|
|
}
|
|
},
|
|
|
|
clickOutside(e) {
|
|
if (this.site.mobileView) {
|
|
this.clickOutsideMobile(e);
|
|
} else {
|
|
this.sendWidgetAction("toggleHamburger");
|
|
}
|
|
}
|
|
});
|