diff --git a/assets/javascripts/discourse/components/wizard-notice-row.js.es6 b/assets/javascripts/discourse/components/wizard-notice-row.js.es6 new file mode 100644 index 00000000..ada4384d --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-notice-row.js.es6 @@ -0,0 +1,19 @@ +import Component from "@ember/component"; +import NoticeMessage from "../mixins/notice-message"; + +export default Component.extend(NoticeMessage, { + tagName: "tr", + attributeBindings: ["notice.id:data-notice-id"], + classNameBindings: [ + ":wizard-notice-row", + "notice.typeClass", + "notice.expired:expired", + "notice.dismissed:dismissed", + ], + + actions: { + dismiss() { + this.notice.dismiss(); + }, + }, +}); diff --git a/assets/javascripts/discourse/components/wizard-notice.js.es6 b/assets/javascripts/discourse/components/wizard-notice.js.es6 index fcd77606..ca6b7658 100644 --- a/assets/javascripts/discourse/components/wizard-notice.js.es6 +++ b/assets/javascripts/discourse/components/wizard-notice.js.es6 @@ -1,35 +1,15 @@ import Component from "@ember/component"; -import discourseComputed from "discourse-common/utils/decorators"; -import { not, notEmpty } from "@ember/object/computed"; -import I18n from "I18n"; +import NoticeMessage from "../mixins/notice-message"; -export default Component.extend({ +export default Component.extend(NoticeMessage, { + attributeBindings: ["notice.id:data-notice-id"], classNameBindings: [ ":wizard-notice", - "notice.type", - "dismissed", - "expired", - "resolved", + "notice.typeClass", + "notice.dismissed:dismissed", + "notice.expired:expired", + "notice.hidden:hidden", ], - showFull: false, - resolved: notEmpty("notice.expired_at"), - dismissed: notEmpty("notice.dismissed_at"), - canDismiss: not("dismissed"), - - @discourseComputed("notice.type") - title(type) { - return I18n.t(`admin.wizard.notice.title.${type}`); - }, - - @discourseComputed("notice.type") - icon(type) { - return { - plugin_status_warning: "exclamation-circle", - plugin_status_connection_error: "bolt", - subscription_messages_connection_error: "bolt", - info: "info-circle", - }[type]; - }, actions: { dismiss() { @@ -38,5 +18,12 @@ export default Component.extend({ this.set("dismissing", false); }); }, + + hide() { + this.set("hiding", true); + this.notice.hide().then(() => { + this.set("hiding", false); + }); + }, }, }); diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs new file mode 100644 index 00000000..9d96bed9 --- /dev/null +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs @@ -0,0 +1,5 @@ +{{#if notices}} + {{#each notices as |notice|}} + {{wizard-notice notice=notice showPlugin=true}} + {{/each}} +{{/if}} diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 new file mode 100644 index 00000000..803e58a4 --- /dev/null +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 @@ -0,0 +1,19 @@ +import { getOwner } from "discourse-common/lib/get-owner"; + +export default { + shouldRender(attrs, ctx) { + return ctx.siteSettings.wizard_critical_notices_on_dashboard; + }, + + setupComponent(attrs, component) { + const controller = getOwner(this).lookup("controller:admin-dashboard"); + + component.set("notices", controller.get("customWizardCriticalNotices")); + controller.addObserver("customWizardCriticalNotices.[]", () => { + if (this._state === "destroying") { + return; + } + component.set("notices", controller.get("customWizardCriticalNotices")); + }); + }, +}; diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs deleted file mode 100644 index 9b01c468..00000000 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if importantNotice}} - {{wizard-notice notice=importantNotice importantOnDashboard=true}} -{{/if}} diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 deleted file mode 100644 index 5962f255..00000000 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 +++ /dev/null @@ -1,16 +0,0 @@ -import { getOwner } from "discourse-common/lib/get-owner"; - -export default { - shouldRender(attrs, ctx) { - return ctx.siteSettings.wizard_important_notices_on_dashboard; - }, - - setupComponent() { - const controller = getOwner(this).lookup("controller:admin-dashboard"); - const importantNotice = controller.get("customWizardImportantNotice"); - - if (importantNotice) { - this.set("importantNotice", importantNotice); - } - }, -}; diff --git a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 new file mode 100644 index 00000000..1721e699 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 @@ -0,0 +1,68 @@ +import Controller from "@ember/controller"; +import CustomWizardNotice from "../models/custom-wizard-notice"; +import discourseComputed from "discourse-common/utils/decorators"; +import { notEmpty } from "@ember/object/computed"; +import { A } from "@ember/array"; +import I18n from "I18n"; + +export default Controller.extend({ + messageUrl: "https://thepavilion.io/t/3652", + messageKey: "info", + messageIcon: "info-circle", + messageClass: "info", + hasNotices: notEmpty("notices"), + page: 0, + loadingMore: false, + canLoadMore: true, + + @discourseComputed("notices.[]", "notices.@each.dismissed") + allDismisssed(notices) { + return notices.every((n) => !n.canDismiss || n.dismissed); + }, + + loadMoreNotices() { + if (!this.canLoadMore) { + return; + } + const page = this.get("page"); + this.set("loadingMore", true); + + CustomWizardNotice.list({ page, include_all: true }) + .then((result) => { + if (result.notices.length === 0) { + this.set("canLoadMore", false); + return; + } + + this.get("notices").pushObjects( + A(result.notices.map((notice) => CustomWizardNotice.create(notice))) + ); + }) + .finally(() => this.set("loadingMore", false)); + }, + + actions: { + loadMore() { + if (this.canLoadMore) { + this.set("page", this.page + 1); + this.loadMoreNotices(); + } + }, + + dismissAll() { + bootbox.confirm( + I18n.t("admin.wizard.notice.dismiss_all.confirm"), + I18n.t("no_value"), + I18n.t("yes_value"), + (result) => { + if (result) { + this.set("loadingMore", true); + CustomWizardNotice.dismissAll().finally(() => + this.set("loadingMore", false) + ); + } + } + ); + }, + }, +}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 index d128d851..33841460 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -1,20 +1,26 @@ -import Controller from "@ember/controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import { ajax } from "discourse/lib/ajax"; +import Controller, { inject as controller } from "@ember/controller"; +import { isPresent } from "@ember/utils"; +import { A } from "@ember/array"; export default Controller.extend({ - actions: { - dismissNotice(noticeId) { - ajax(`/admin/wizards/notice/${this.id}`, { - type: "DELETE", - }) - .then((result) => { - if (result.success) { - const notices = this.notices; - notices.removeObject(notices.findBy("id", noticeId)); - } - }) - .catch(popupAjaxError); - }, + adminWizardsNotices: controller(), + + unsubscribe() { + this.messageBus.unsubscribe("/custom-wizard/notices"); + }, + + subscribe() { + this.unsubscribe(); + this.messageBus.subscribe("/custom-wizard/notices", (data) => { + if (isPresent(data.active_notice_count)) { + this.set("activeNoticeCount", data.active_notice_count); + this.adminWizardsNotices.setProperties({ + notices: A(), + page: 0, + canLoadMore: true, + }); + this.adminWizardsNotices.loadMoreNotices(); + } + }); }, }); diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index 67b91f87..c3c95e48 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -63,6 +63,11 @@ export default { path: "/subscription", resetNamespace: true, }); + + this.route("adminWizardsNotices", { + path: "/notices", + resetNamespace: true, + }); } ); }, diff --git a/assets/javascripts/discourse/helpers/notice-badge.js.es6 b/assets/javascripts/discourse/helpers/notice-badge.js.es6 new file mode 100644 index 00000000..ea32b462 --- /dev/null +++ b/assets/javascripts/discourse/helpers/notice-badge.js.es6 @@ -0,0 +1,43 @@ +import { autoUpdatingRelativeAge } from "discourse/lib/formatter"; +import { iconHTML } from "discourse-common/lib/icon-library"; +import I18n from "I18n"; +import { registerUnbound } from "discourse-common/lib/helpers"; +import { htmlSafe } from "@ember/template"; + +registerUnbound("notice-badge", function (attrs) { + let tag = attrs.url ? "a" : "div"; + let attrStr = ""; + if (attrs.title) { + attrStr += `title='${I18n.t(attrs.title)}'`; + } + if (attrs.url) { + attrStr += `href='${attrs.url}'`; + } + let html = `<${tag} class="${ + attrs.class ? `${attrs.class} ` : "" + }notice-badge" ${attrStr}>`; + if (attrs.icon) { + html += iconHTML(attrs.icon); + } + if (attrs.label) { + if (attrs.icon) { + html += " "; + } + html += `${I18n.t(attrs.label)}`; + } + if (attrs.date) { + if (attrs.icon || attrs.label) { + html += " "; + } + let dateAttrs = {}; + if (attrs.leaveAgo) { + dateAttrs = { + format: "medium", + leaveAgo: true, + }; + } + html += autoUpdatingRelativeAge(new Date(attrs.date), dateAttrs); + } + html += `${tag}>`; + return htmlSafe(html); +}); diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 21a9d745..e67f1ebd 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -1,6 +1,7 @@ import DiscourseURL from "discourse/lib/url"; import { withPluginApi } from "discourse/lib/plugin-api"; import CustomWizardNotice from "../models/custom-wizard-notice"; +import { isPresent } from "@ember/utils"; import { A } from "@ember/array"; export default { @@ -22,35 +23,43 @@ export default { withPluginApi("0.8.36", (api) => { api.modifyClass("route:admin-dashboard", { - afterModel() { - return CustomWizardNotice.list().then((result) => { - if (result && result.length) { - this.set( - "notices", - A(result.map((n) => CustomWizardNotice.create(n))) - ); + setupController(controller) { + this._super(...arguments); + + controller.loadCriticalNotices(); + controller.subscribe(); + }, + }); + + api.modifyClass("controller:admin-dashboard", { + criticalNotices: A(), + + unsubscribe() { + this.messageBus.unsubscribe("/custom-wizard/notices"); + }, + + subscribe() { + this.unsubscribe(); + this.messageBus.subscribe("/custom-wizard/notices", (data) => { + if (isPresent(data.active_notice_count)) { + this.loadCriticalNotices(); } }); }, - setupController(controller) { - if (this.notices) { - let pluginStatusConnectionError = this.notices.filter( - (n) => n.type === "plugin_status_connection_error" - )[0]; - let pluginStatusWarning = this.notices.filter( - (n) => n.type === "plugin_status_warning" - )[0]; - - if (pluginStatusConnectionError || pluginStatusWarning) { - controller.set( - "customWizardImportantNotice", - pluginStatusConnectionError || pluginStatusWarning + loadCriticalNotices() { + CustomWizardNotice.list({ + type: ["connection_error", "warning"], + archetype: "plugin_status", + visible: true, + }).then((result) => { + if (result.notices && result.notices.length) { + const criticalNotices = A( + result.notices.map((n) => CustomWizardNotice.create(n)) ); + this.set("customWizardCriticalNotices", criticalNotices); } - } - - this._super(...arguments); + }); }, }); }); diff --git a/assets/javascripts/discourse/mixins/notice-message.js.es6 b/assets/javascripts/discourse/mixins/notice-message.js.es6 new file mode 100644 index 00000000..76e311bb --- /dev/null +++ b/assets/javascripts/discourse/mixins/notice-message.js.es6 @@ -0,0 +1,68 @@ +import Mixin from "@ember/object/mixin"; +import { bind, scheduleOnce } from "@ember/runloop"; +import { cookAsync } from "discourse/lib/text"; +import { createPopper } from "@popperjs/core"; + +export default Mixin.create({ + showCookedMessage: false, + + didReceiveAttrs() { + const message = this.notice.message; + cookAsync(message).then((cooked) => { + this.set("cookedMessage", cooked); + }); + }, + + createMessageModal() { + let container = this.element.querySelector(".notice-message"); + let modal = this.element.querySelector(".cooked-notice-message"); + + this._popper = createPopper(container, modal, { + strategy: "absolute", + placement: "bottom-start", + modifiers: [ + { + name: "preventOverflow", + }, + { + name: "offset", + options: { + offset: [0, 5], + }, + }, + ], + }); + }, + + didInsertElement() { + $(document).on("click", bind(this, this.documentClick)); + }, + + willDestroyElement() { + $(document).off("click", bind(this, this.documentClick)); + }, + + documentClick(event) { + if (this._state === "destroying") { + return; + } + + if ( + !event.target.closest( + `[data-notice-id="${this.notice.id}"] .notice-message` + ) + ) { + this.set("showCookedMessage", false); + } + }, + + actions: { + toggleCookedMessage() { + this.toggleProperty("showCookedMessage"); + + if (this.showCookedMessage) { + scheduleOnce("afterRender", this, this.createMessageModal); + } + }, + }, +}); diff --git a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 index 1eb21e73..035e2ad5 100644 --- a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 @@ -1,12 +1,38 @@ import EmberObject from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import { and, not, notEmpty } from "@ember/object/computed"; +import { dasherize } from "@ember/string"; +import I18n from "I18n"; -const CustomWizardNotice = EmberObject.extend(); +const CustomWizardNotice = EmberObject.extend({ + expired: notEmpty("expired_at"), + dismissed: notEmpty("dismissed_at"), + hidden: notEmpty("hidden_at"), + notHidden: not("hidden"), + notDismissed: not("dismissed"), + canDismiss: and("dismissable", "notDismissed"), + canHide: and("can_hide", "notHidden"), + + @discourseComputed("type") + typeClass(type) { + return dasherize(type); + }, + + @discourseComputed("type") + typeLabel(type) { + return I18n.t(`admin.wizard.notice.type.${type}`); + }, -CustomWizardNotice.reopen({ dismiss() { - return ajax(`/admin/wizards/notice/${this.id}`, { type: "PUT" }) + if (!this.get("canDismiss")) { + return; + } + + return ajax(`/admin/wizards/notice/${this.get("id")}/dismiss`, { + type: "PUT", + }) .then((result) => { if (result.success) { this.set("dismissed_at", result.dismissed_at); @@ -14,11 +40,34 @@ CustomWizardNotice.reopen({ }) .catch(popupAjaxError); }, + + hide() { + if (!this.get("canHide")) { + return; + } + + return ajax(`/admin/wizards/notice/${this.get("id")}/hide`, { type: "PUT" }) + .then((result) => { + if (result.success) { + this.set("hidden_at", result.hidden_at); + } + }) + .catch(popupAjaxError); + }, }); CustomWizardNotice.reopenClass({ - list() { - return ajax("/admin/wizards/notice").catch(popupAjaxError); + list(data = {}) { + return ajax("/admin/wizards/notice", { + type: "GET", + data, + }).catch(popupAjaxError); + }, + + dismissAll() { + return ajax("/admin/wizards/notice/dismiss", { + type: "PUT", + }).catch(popupAjaxError); }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 new file mode 100644 index 00000000..1d8b7cc8 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 @@ -0,0 +1,17 @@ +import CustomWizardNotice from "../models/custom-wizard-notice"; +import DiscourseRoute from "discourse/routes/discourse"; +import { A } from "@ember/array"; + +export default DiscourseRoute.extend({ + model() { + return CustomWizardNotice.list({ include_all: true }); + }, + + setupController(controller, model) { + controller.setProperties({ + notices: A( + model.notices.map((notice) => CustomWizardNotice.create(notice)) + ), + }); + }, +}); diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 index de67a8b9..5c39c0d6 100644 --- a/assets/javascripts/discourse/routes/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -1,6 +1,5 @@ import DiscourseRoute from "discourse/routes/discourse"; import { ajax } from "discourse/lib/ajax"; -import { A } from "@ember/array"; export default DiscourseRoute.extend({ model() { @@ -8,8 +7,16 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - controller.set("notices", A(model.notices)); controller.set("api_section", model.api_section); + + if (model.active_notice_count) { + controller.set("activeNoticeCount", model.active_notice_count); + } + if (model.featured_notices) { + controller.set("featuredNotices", model.featured_notices); + } + + controller.subscribe(); }, afterModel(model, transition) { diff --git a/assets/javascripts/discourse/templates/admin-wizards-notices.hbs b/assets/javascripts/discourse/templates/admin-wizards-notices.hbs new file mode 100644 index 00000000..039afe49 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-notices.hbs @@ -0,0 +1,49 @@ +
{{I18n "admin.wizard.notice.time"}} | +{{I18n "admin.wizard.notice.type.label"}} | +{{I18n "admin.wizard.notice.title"}} | +{{I18n "admin.wizard.notice.status"}} | +
---|
{{i18n "search.no_results"}}
+ {{/unless}} + {{/if}} + + {{conditional-loading-spinner condition=loadingMore}} + {{/load-more}} +