149 lines
3.5 KiB
JavaScript
149 lines
3.5 KiB
JavaScript
import Session from "discourse/models/session";
|
|
import { createWidget } from "discourse/widgets/widget";
|
|
import { h } from "virtual-dom";
|
|
import { headerHeight } from "discourse/components/site-header";
|
|
import { Promise } from "rsvp";
|
|
|
|
// even a 2 liner notification should be under 50px in default view
|
|
const AVERAGE_ITEM_HEIGHT = 50;
|
|
|
|
// our UX usually carries about 100px of padding around the notification excluding header
|
|
const PADDING = 100;
|
|
|
|
/**
|
|
* This tries to enforce a consistent flow of fetching, caching, refreshing,
|
|
* and rendering for "quick access items".
|
|
*
|
|
* There are parts to introducing a new quick access panel:
|
|
* 1. A user menu link that sends a `quickAccess` action, with a unique `type`.
|
|
* 2. A `quick-access-${type}` widget, extended from `quick-access-panel`.
|
|
*/
|
|
export default createWidget("quick-access-panel", {
|
|
tagName: "div.quick-access-panel",
|
|
emptyStatePlaceholderItemKey: "",
|
|
|
|
buildKey: () => {
|
|
throw Error('Cannot attach abstract widget "quick-access-panel".');
|
|
},
|
|
|
|
markReadRequest() {
|
|
return Promise.resolve();
|
|
},
|
|
|
|
hasUnread() {
|
|
return false;
|
|
},
|
|
|
|
showAllHref() {
|
|
return "";
|
|
},
|
|
|
|
hasMore() {
|
|
return this.getItems().length >= this.estimateItemLimit();
|
|
},
|
|
|
|
findNewItems() {
|
|
return Promise.resolve([]);
|
|
},
|
|
|
|
newItemsLoaded() {},
|
|
|
|
itemHtml(item) {}, // eslint-disable-line no-unused-vars
|
|
|
|
emptyStatePlaceholderItem() {
|
|
if (this.emptyStatePlaceholderItemKey) {
|
|
return h("li.read", I18n.t(this.emptyStatePlaceholderItemKey));
|
|
} else {
|
|
return "";
|
|
}
|
|
},
|
|
|
|
defaultState() {
|
|
return { items: [], loading: false, loaded: false };
|
|
},
|
|
|
|
markRead() {
|
|
return this.markReadRequest().then(() => {
|
|
this.refreshNotifications(this.state);
|
|
});
|
|
},
|
|
|
|
estimateItemLimit() {
|
|
// Estimate (poorly) the amount of notifications to return.
|
|
let limit = Math.round(
|
|
($(window).height() - headerHeight() - PADDING) / AVERAGE_ITEM_HEIGHT
|
|
);
|
|
|
|
// We REALLY don't want to be asking for negative counts of notifications
|
|
// less than 5 is also not that useful.
|
|
if (limit < 5) {
|
|
limit = 5;
|
|
} else if (limit > 40) {
|
|
limit = 40;
|
|
}
|
|
|
|
return limit;
|
|
},
|
|
|
|
refreshNotifications(state) {
|
|
if (this.loading) {
|
|
return;
|
|
}
|
|
|
|
if (this.getItems().length === 0) {
|
|
state.loading = true;
|
|
}
|
|
|
|
this.findNewItems()
|
|
.then(newItems => this.setItems(newItems))
|
|
.catch(() => this.setItems([]))
|
|
.finally(() => {
|
|
state.loading = false;
|
|
state.loaded = true;
|
|
this.newItemsLoaded();
|
|
this.sendWidgetAction("itemsLoaded", {
|
|
hasUnread: this.hasUnread(),
|
|
markRead: () => this.markRead()
|
|
});
|
|
this.scheduleRerender();
|
|
});
|
|
},
|
|
|
|
html(attrs, state) {
|
|
if (!state.loaded) {
|
|
this.refreshNotifications(state);
|
|
}
|
|
|
|
if (state.loading) {
|
|
return [h("div.spinner-container", h("div.spinner"))];
|
|
}
|
|
|
|
const items = this.getItems().length
|
|
? this.getItems().map(item => this.itemHtml(item))
|
|
: [this.emptyStatePlaceholderItem()];
|
|
|
|
if (this.hasMore()) {
|
|
items.push(
|
|
h(
|
|
"li.read.last.show-all",
|
|
this.attach("link", {
|
|
title: "view_all",
|
|
icon: "chevron-down",
|
|
href: this.showAllHref()
|
|
})
|
|
)
|
|
);
|
|
}
|
|
|
|
return [h("ul", items)];
|
|
},
|
|
|
|
getItems() {
|
|
return Session.currentProp(`${this.key}-items`) || [];
|
|
},
|
|
|
|
setItems(newItems) {
|
|
Session.currentProp(`${this.key}-items`, newItems);
|
|
}
|
|
});
|