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..9c099b39 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-notice-row.js.es6 @@ -0,0 +1,14 @@ +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(); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-notice.js.es6 b/assets/javascripts/discourse/components/wizard-notice.js.es6 index fcd77606..cac3e4eb 100644 --- a/assets/javascripts/discourse/components/wizard-notice.js.es6 +++ b/assets/javascripts/discourse/components/wizard-notice.js.es6 @@ -1,35 +1,9 @@ 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({ - classNameBindings: [ - ":wizard-notice", - "notice.type", - "dismissed", - "expired", - "resolved", - ], - 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]; - }, +export default Component.extend(NoticeMessage, { + attributeBindings: ["notice.id:data-notice-id"], + classNameBindings: [':wizard-notice', 'notice.typeClass', 'notice.dismissed:dismissed', 'notice.expired:expired', 'notice.hidden:hidden'], actions: { dismiss() { @@ -38,5 +12,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..0bb252e9 --- /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')); + }); + } +}; \ No newline at end of file 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..0f67f878 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 @@ -0,0 +1,67 @@ +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..e2672fe4 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..bc5df4a6 --- /dev/null +++ b/assets/javascripts/discourse/helpers/notice-badge.js.es6 @@ -0,0 +1,41 @@ +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); +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 21a9d745..8208cd20 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 { @@ -21,37 +22,46 @@ 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))) - ); + api.modifyClass('route:admin-dashboard', { + 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..492df643 --- /dev/null +++ b/assets/javascripts/discourse/mixins/notice-message.js.es6 @@ -0,0 +1,65 @@ +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); + } + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 index 1eb21e73..29e30628 100644 --- a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 @@ -1,25 +1,68 @@ 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'), -CustomWizardNotice.reopen({ - dismiss() { - return ajax(`/admin/wizards/notice/${this.id}`, { type: "PUT" }) - .then((result) => { - if (result.success) { - this.set("dismissed_at", result.dismissed_at); - } - }) - .catch(popupAjaxError); + @discourseComputed('type') + typeClass(type) { + return dasherize(type); }, + + @discourseComputed('type') + typeLabel(type) { + return I18n.t(`admin.wizard.notice.type.${type}`); + }, + + dismiss() { + 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); + } + }).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); + } }); export default CustomWizardNotice; 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..a329ce95 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 @@ -0,0 +1,15 @@ +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..2bbc73a9 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,13 +7,21 @@ 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) { if (transition.targetName === "adminWizards.index") { this.transitionTo("adminWizardsWizard"); } - }, + } }); 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..d522c1a5 --- /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}} +