FEATURE: allows plugins to add a global notice (#8552)

* FEATURE: allows plugins to add a global notice

Usage:

```
api.addGlobalNotice(id, text, options = {});
```

Options can be:

```
dismissable // Will display a button to hide the notice if true
html // will prepend html to the next if present
level // alert level, will usee css class of alert component
persistentDismiss // if true won't show notice again on reload
onDismiss // execute a custom action on dismiss
visibility // defines custom logic for notice visibility
```

Co-authored-by: Robin Ward <robin.ward@gmail.com>
This commit is contained in:
Joffrey JAFFEUX
2019-12-27 09:06:36 +01:00
committed by GitHub
parent 1820347d58
commit c25b8abd70
4 changed files with 215 additions and 85 deletions

View File

@@ -1,119 +1,208 @@
import { bind } from "@ember/runloop";
import { bind, cancel } from "@ember/runloop";
import Component from "@ember/component";
import { on } from "discourse-common/utils/decorators";
import { iconHTML } from "discourse-common/lib/icon-library";
import LogsNotice from "discourse/services/logs-notice";
import { bufferedRender } from "discourse-common/lib/buffered-render";
import EmberObject from "@ember/object";
export default Component.extend(
bufferedRender({
rerenderTriggers: ["site.isReadOnly", "siteSettings.disable_emails"],
const _pluginNotices = [];
buildBuffer(buffer) {
export function addGlobalNotice(text, id, options = {}) {
_pluginNotices.push(Notice.create({ text, id, options }));
}
const GLOBAL_NOTICE_DISMISSED_PROMPT_KEY = "dismissed-global-notice";
const Notice = EmberObject.extend({
text: null,
id: null,
options: null,
init() {
this._super(...arguments);
const defaults = {
dismissable: false,
html: null,
level: "info",
persistentDismiss: true,
onDismiss: null,
visibility: null
};
this.options = this.set(
"options",
Object.assign(defaults, this.options || {})
);
}
});
export default Component.extend({
logNotice: null,
init() {
this._super(...arguments);
this._setupObservers();
},
willDestroyElement() {
this._super(...arguments);
this._tearDownObservers();
},
notices: Ember.computed(
"site.isReadOnly",
"siteSettings.disable_emails",
"logNotice.{id,text,hidden}",
function() {
let notices = [];
if ($.cookie("dosp") === "1") {
$.removeCookie("dosp", { path: "/" });
notices.push([I18n.t("forced_anonymous"), "forced-anonymous"]);
notices.push(
Notice.create({
text: I18n.t("forced_anonymous"),
id: "forced-anonymous"
})
);
}
if (this.session.get("safe_mode")) {
notices.push([I18n.t("safe_mode.enabled"), "safe-mode"]);
if (this.session && this.session.safe_mode) {
notices.push(
Notice.create({ text: I18n.t("safe_mode.enabled"), id: "safe-mode" })
);
}
if (this.site.get("isReadOnly")) {
notices.push([I18n.t("read_only_mode.enabled"), "alert-read-only"]);
if (this.site.isReadOnly) {
notices.push(
Notice.create({
text: I18n.t("read_only_mode.enabled"),
id: "alert-read-only"
})
);
}
if (
this.siteSettings.disable_emails === "yes" ||
this.siteSettings.disable_emails === "non-staff"
) {
notices.push([I18n.t("emails_are_disabled"), "alert-emails-disabled"]);
notices.push(
Notice.create({
text: I18n.t("emails_are_disabled"),
id: "alert-emails-disabled"
})
);
}
if (this.site.get("wizard_required")) {
if (this.site.wizard_required) {
const requiredText = I18n.t("wizard_required", {
url: Discourse.getURL("/wizard")
});
notices.push([requiredText, "alert-wizard"]);
notices.push(Notice.create({ text: requiredText, id: "alert-wizard" }));
}
if (
this.currentUser &&
this.currentUser.get("staff") &&
this.get("currentUser.staff") &&
this.siteSettings.bootstrap_mode_enabled
) {
if (this.siteSettings.bootstrap_mode_min_users > 0) {
notices.push([
I18n.t("bootstrap_mode_enabled", {
min_users: this.siteSettings.bootstrap_mode_min_users
}),
"alert-bootstrap-mode"
]);
notices.push(
Notice.create({
text: I18n.t("bootstrap_mode_enabled", {
min_users: this.siteSettings.bootstrap_mode_min_users
}),
id: "alert-bootstrap-mode"
})
);
} else {
notices.push([
I18n.t("bootstrap_mode_disabled"),
"alert-bootstrap-mode"
]);
notices.push(
Notice.create({
text: I18n.t("bootstrap_mode_disabled"),
id: "alert-bootstrap-mode"
})
);
}
}
if (!_.isEmpty(this.siteSettings.global_notice)) {
notices.push([this.siteSettings.global_notice, "alert-global-notice"]);
}
if (!LogsNotice.currentProp("hidden")) {
notices.push([
LogsNotice.currentProp("message"),
"alert-logs-notice",
`<button class='btn btn-flat close'>${iconHTML("times")}</button>`
]);
}
if (notices.length > 0) {
buffer.push(
notices
.map(n => {
var html = `<div class='row'><div class='alert alert-info ${n[1]}'>`;
if (n[2]) html += n[2];
html += `${n[0]}</div></div>`;
return html;
})
.join("")
if (
this.siteSettings.global_notice &&
this.siteSettings.global_notice.length
) {
notices.push(
Notice.create({
text: this.siteSettings.global_notice,
id: "alert-global-notice"
})
);
}
},
@on("didInsertElement")
_setupLogsNotice() {
this._boundRerenderBuffer = bind(this, this.rerenderBuffer);
LogsNotice.current().addObserver("hidden", this._boundRerenderBuffer);
this._boundResetCurrentProp = bind(this, this._resetCurrentProp);
$(this.element).on(
"click.global-notice",
".alert-logs-notice .close",
this._boundResetCurrentProp
);
},
@on("willDestroyElement")
_teardownLogsNotice() {
if (this._boundResetCurrentProp) {
$(this.element).off("click.global-notice", this._boundResetCurrentProp);
if (this.logNotice) {
notices.push(this.logNotice);
}
if (this._boundRerenderBuffer) {
LogsNotice.current().removeObserver(
"hidden",
this._boundRerenderBuffer
);
}
},
_resetCurrentProp() {
LogsNotice.currentProp("text", "");
return notices.concat(_pluginNotices).filter(notice => {
if (notice.options.visibility) {
return notice.options.visibility(notice);
} else {
const key = `${GLOBAL_NOTICE_DISMISSED_PROMPT_KEY}-${notice.id}`;
return !this.keyValueStore.get(key);
}
});
}
})
);
),
actions: {
dismissNotice(notice) {
if (notice.options.onDismiss) {
notice.options.onDismiss(notice);
}
if (notice.options.persistentDismiss) {
this.keyValueStore.set({
key: `${GLOBAL_NOTICE_DISMISSED_PROMPT_KEY}-${notice.id}`,
value: true
});
}
const alert = document.getElementById(`global-notice-${notice.id}`);
if (alert) alert.style.display = "none";
}
},
_setupObservers() {
this._boundLogsNoticeHandler = bind(this, this._handleLogsNoticeUpdate);
LogsNotice.current().addObserver("hidden", this._boundLogsNoticeHandler);
LogsNotice.current().addObserver("text", this._boundLogsNoticeHandler);
},
_tearDownObservers() {
if (this._boundLogsNoticeHandler) {
LogsNotice.current().removeObserver("text", this._boundLogsNoticeHandler);
LogsNotice.current().removeObserver(
"hidden",
this._boundLogsNoticeHandler
);
cancel(this._boundLogsNoticeHandler);
}
},
_handleLogsNoticeUpdate() {
const logNotice = Notice.create({
text: LogsNotice.currentProp("message"),
id: "alert-logs-notice",
options: {
dismissable: true,
persistentDismiss: false,
visibility() {
return !LogsNotice.currentProp("hidden");
},
onDismiss() {
LogsNotice.currentProp("hidden", true);
LogsNotice.currentProp("text", "");
}
}
});
this.set("logNotice", logNotice);
}
});

View File

@@ -8,6 +8,7 @@ import { includeAttributes } from "discourse/lib/transform-post";
import { registerHighlightJSLanguage } from "discourse/lib/highlight-syntax";
import { addToolbarCallback } from "discourse/components/d-editor";
import { addWidgetCleanCallback } from "discourse/components/mount-widget";
import { addGlobalNotice } from "discourse/components/global-notice";
import {
createWidget,
reopenWidget,
@@ -49,7 +50,7 @@ import { queryRegistry } from "discourse/widgets/widget";
import Composer from "discourse/models/composer";
// If you add any methods to the API ensure you bump up this number
const PLUGIN_API_VERSION = "0.8.36";
const PLUGIN_API_VERSION = "0.8.37";
class PluginApi {
constructor(version, container) {
@@ -969,6 +970,18 @@ class PluginApi {
registerHighlightJSLanguage(name, fn) {
registerHighlightJSLanguage(name, fn);
}
/**
* Adds global notices to display.
*
* Example:
*
* api.addGlobalNotice("text", "foo", { html: "<p>bar</p>" })
*
**/
addGlobalNotice(id, text, options) {
addGlobalNotice(id, text, options);
}
}
let _pluginv01;

View File

@@ -0,0 +1,19 @@
{{#each notices as |notice|}}
<div class="row">
<div id="global-notice-{{notice.id}}" class="alert alert-{{notice.options.level}} {{notice.id}}">
{{#if notice.options.html}}
{{{notice.options.html}}}
{{/if}}
<span class="text">{{notice.text}}</span>
{{#if notice.options.dismissable}}
{{d-button
class="btn-flat close"
icon="times"
action=(action "dismissNotice")
actionParam=notice
}}
{{/if}}
</div>
</div>
{{/each}}

View File

@@ -1,19 +1,28 @@
.alert {
padding: 8px 32px 8px 16px;
padding: 0.5em 1em;
background-color: $danger-low;
color: $primary;
position: relative;
display: flex;
.close {
position: absolute;
top: 8px;
right: 8px;
font-size: $font-up-3;
align-self: flex-start;
margin-left: auto;
.d-icon {
color: $primary-low-mid;
}
}
.text {
flex: 1 1 auto;
& + .close {
margin-left: 0.5em;
}
}
&.alert-success {
background-color: $success-low;
color: $primary;