mirror of
https://github.com/discourse/discourse.git
synced 2025-02-08 20:34:52 +00:00
FEATURE: Show tooltip for bootstrap mode (#22257)
Improve user tips UX and make them smoother.
This commit is contained in:
parent
8c74bb6573
commit
0b16fc8172
@ -1,3 +1,25 @@
|
|||||||
<a class="btn btn-default bootstrap-mode" href={{this.href}}>
|
<DButton
|
||||||
{{i18n "bootstrap_mode"}}
|
class="btn-default bootstrap-mode"
|
||||||
</a>
|
@label="bootstrap_mode"
|
||||||
|
@action={{this.routeToAdminGuide}}
|
||||||
|
{{did-insert this.setupUserTip}}
|
||||||
|
>
|
||||||
|
{{#if this.showUserTip}}
|
||||||
|
<UserTip
|
||||||
|
@id="admin_guide"
|
||||||
|
@primaryLabel="user_tips.admin_guide.primary"
|
||||||
|
@onDismiss={{this.routeToAdminGuide}}
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
<DTooltip @theme="user-tip" @arrow={{true}}>
|
||||||
|
<div class="user-tip__container">
|
||||||
|
<div class="user-tip__title">
|
||||||
|
{{i18n "user_tips.admin_guide.title"}}
|
||||||
|
</div>
|
||||||
|
<div class="user-tip__content">
|
||||||
|
{{i18n "user_tips.admin_guide.content"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DTooltip>
|
||||||
|
{{/if}}
|
||||||
|
</DButton>
|
@ -1,11 +1,25 @@
|
|||||||
|
import { action } from "@ember/object";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import DiscourseURL from "discourse/lib/url";
|
||||||
|
|
||||||
export default class BootstrapModeNotice extends Component {
|
export default class BootstrapModeNotice extends Component {
|
||||||
|
@service currentUser;
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
|
|
||||||
get href() {
|
@tracked showUserTip = false;
|
||||||
const topicId = this.siteSettings.admin_quick_start_topic_id;
|
|
||||||
return `/t/-/${topicId}`;
|
@action
|
||||||
|
setupUserTip() {
|
||||||
|
this.showUserTip = this.currentUser?.canSeeUserTip("admin_guide");
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
routeToAdminGuide() {
|
||||||
|
this.showUserTip = false;
|
||||||
|
DiscourseURL.routeTo(
|
||||||
|
`/t/-/${this.siteSettings.admin_quick_start_topic_id}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { schedule } from "@ember/runloop";
|
import { schedule } from "@ember/runloop";
|
||||||
|
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||||
import tippy from "tippy.js";
|
import tippy from "tippy.js";
|
||||||
import Ember from "ember";
|
import Ember from "ember";
|
||||||
|
|
||||||
@ -36,8 +37,8 @@ export default class DiscourseTooltip extends Component {
|
|||||||
interactive,
|
interactive,
|
||||||
content: element,
|
content: element,
|
||||||
trigger: this.capabilities.touch ? "click" : "mouseenter",
|
trigger: this.capabilities.touch ? "click" : "mouseenter",
|
||||||
theme: "d-tooltip",
|
theme: this.attrs.theme || "d-tooltip",
|
||||||
arrow: false,
|
arrow: this.attrs.arrow ? iconHTML("tippy-rounded-arrow") : false,
|
||||||
placement: this.placement,
|
placement: this.placement,
|
||||||
onTrigger: this.stopPropagation,
|
onTrigger: this.stopPropagation,
|
||||||
onUntrigger: this.stopPropagation,
|
onUntrigger: this.stopPropagation,
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
role="complementary"
|
role="complementary"
|
||||||
aria-labelledby="suggested-topics-title"
|
aria-labelledby="suggested-topics-title"
|
||||||
>
|
>
|
||||||
<UserTip @id="suggested_topics" />
|
<UserTip @id="suggested_topics" @selector=".user-tip-reference" />
|
||||||
|
|
||||||
<h3 id="suggested-topics-title" class="suggested-topics-title">
|
<h3 id="suggested-topics-title" class="suggested-topics-title">
|
||||||
{{i18n this.suggestedTitleLabel}}
|
{{i18n this.suggestedTitleLabel}}
|
||||||
|
@ -1 +1 @@
|
|||||||
<span {{did-insert this.showUserTip}}></span>
|
<span class="user-tip-reference" {{did-insert this.showUserTip}}></span>
|
@ -15,20 +15,29 @@ export default class UserTip extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
schedule("afterRender", () => {
|
schedule("afterRender", () => {
|
||||||
const { id, selector, content, placement } = this.args;
|
const {
|
||||||
|
id,
|
||||||
|
selector,
|
||||||
|
content,
|
||||||
|
placement,
|
||||||
|
primaryLabel,
|
||||||
|
onDismiss,
|
||||||
|
onDismissAll,
|
||||||
|
} = this.args;
|
||||||
|
element = element.parentElement;
|
||||||
|
|
||||||
this.currentUser.showUserTip({
|
this.currentUser.showUserTip({
|
||||||
id,
|
id,
|
||||||
|
|
||||||
titleText: I18n.t(`user_tips.${id}.title`),
|
titleText: I18n.t(`user_tips.${id}.title`),
|
||||||
contentText: content || I18n.t(`user_tips.${id}.content`),
|
contentText: content || I18n.t(`user_tips.${id}.content`),
|
||||||
|
primaryText: primaryLabel ? I18n.t(primaryLabel) : null,
|
||||||
reference: selector
|
reference:
|
||||||
? element.parentElement.querySelector(selector) ||
|
(selector && element.parentElement.querySelector(selector)) ||
|
||||||
element.parentElement
|
element,
|
||||||
: element,
|
|
||||||
appendTo: element.parentElement,
|
appendTo: element.parentElement,
|
||||||
|
placement,
|
||||||
placement: placement || "top",
|
onDismiss,
|
||||||
|
onDismissAll,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,53 +3,92 @@ import { iconHTML } from "discourse-common/lib/icon-library";
|
|||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import { escape } from "pretty-text/sanitizer";
|
import { escape } from "pretty-text/sanitizer";
|
||||||
import tippy from "tippy.js";
|
import tippy from "tippy.js";
|
||||||
|
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
||||||
|
|
||||||
const instances = {};
|
const TIPPY_DELAY = 500;
|
||||||
const queue = [];
|
|
||||||
|
const instancesMap = {};
|
||||||
|
window.instancesMap = instancesMap;
|
||||||
|
|
||||||
|
function destroyInstance(instance) {
|
||||||
|
if (instance.showTimeout) {
|
||||||
|
clearTimeout(instance.showTimeout);
|
||||||
|
instance.showTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance.destroyTimeout) {
|
||||||
|
clearTimeout(instance.destroyTimeout);
|
||||||
|
instance.destroyTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelDestroyInstance(instance) {
|
||||||
|
if (instance.destroyTimeout) {
|
||||||
|
clearTimeout(instance.destroyTimeout);
|
||||||
|
instance.destroyTimeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showInstance(instance) {
|
||||||
|
if (isTesting()) {
|
||||||
|
instance.show();
|
||||||
|
} else if (!instance.showTimeout) {
|
||||||
|
instance.showTimeout = setTimeout(() => {
|
||||||
|
instance.showTimeout = null;
|
||||||
|
if (!instance.state.isDestroyed) {
|
||||||
|
instance.show();
|
||||||
|
}
|
||||||
|
}, TIPPY_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideInstance(instance) {
|
||||||
|
clearTimeout(instance.showTimeout);
|
||||||
|
instance.showTimeout = null;
|
||||||
|
instance.hide();
|
||||||
|
}
|
||||||
|
|
||||||
export function showUserTip(options) {
|
export function showUserTip(options) {
|
||||||
hideUserTip(options.id);
|
// Find if a similar instance has been scheduled for destroying recently
|
||||||
|
// and cancel that
|
||||||
|
let instance = instancesMap[options.id];
|
||||||
|
if (instance) {
|
||||||
|
if (instance.reference === options.reference) {
|
||||||
|
return cancelDestroyInstance(instance);
|
||||||
|
} else {
|
||||||
|
destroyInstance(instance);
|
||||||
|
delete instancesMap[options.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!options.reference) {
|
if (!options.reference) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(instances).length > 0) {
|
instancesMap[options.id] = tippy(options.reference, {
|
||||||
return addToQueue(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
instances[options.id] = tippy(options.reference, {
|
|
||||||
// Tippy must be displayed as soon as possible and not be hidden unless
|
|
||||||
// the user clicks on one of the two buttons.
|
|
||||||
showOnCreate: true,
|
|
||||||
hideOnClick: false,
|
hideOnClick: false,
|
||||||
trigger: "manual",
|
trigger: "manual",
|
||||||
theme: "user-tips",
|
theme: "user-tip",
|
||||||
zIndex: "",
|
zIndex: "", // reset z-index to use inherited value from the parent
|
||||||
delay: isTesting() ? 0 : 100,
|
duration: TIPPY_DELAY,
|
||||||
|
|
||||||
// It must be interactive to make buttons work.
|
|
||||||
interactive: true,
|
|
||||||
|
|
||||||
arrow: iconHTML("tippy-rounded-arrow"),
|
arrow: iconHTML("tippy-rounded-arrow"),
|
||||||
placement: options.placement,
|
placement: options.placement,
|
||||||
appendTo: options.appendTo,
|
appendTo: options.appendTo,
|
||||||
|
|
||||||
// It often happens for the reference element to be rerendered. In this
|
interactive: true, // for buttons in content
|
||||||
// case, tippy must be rerendered too. Having an animation means that the
|
|
||||||
// animation will replay over and over again.
|
|
||||||
animation: false,
|
|
||||||
|
|
||||||
// The `content` property below is HTML.
|
|
||||||
allowHTML: true,
|
allowHTML: true,
|
||||||
|
|
||||||
content: `
|
content:
|
||||||
<div class='user-tip-container'>
|
options.content ||
|
||||||
<div class='user-tip-title'>${escape(options.titleText)}</div>
|
`<div class='user-tip__container'>
|
||||||
<div class='user-tip-content'>${escape(options.contentText)}</div>
|
<div class='user-tip__title'>${escape(options.titleText)}</div>
|
||||||
<div class='user-tip-buttons'>
|
<div class='user-tip__content'>${escape(options.contentText)}</div>
|
||||||
|
<div class='user-tip__buttons'>
|
||||||
<button class="btn btn-primary btn-dismiss">${escape(
|
<button class="btn btn-primary btn-dismiss">${escape(
|
||||||
options.primaryBtnText || I18n.t("user_tips.primary")
|
options.primaryText || I18n.t("user_tips.primary")
|
||||||
)}</button>
|
)}</button>
|
||||||
<button class="btn btn-flat btn-text btn-dismiss-all">${escape(
|
<button class="btn btn-flat btn-text btn-dismiss-all">${escape(
|
||||||
options.secondaryBtnText || I18n.t("user_tips.secondary")
|
options.secondaryBtnText || I18n.t("user_tips.secondary")
|
||||||
@ -57,57 +96,86 @@ export function showUserTip(options) {
|
|||||||
</div>
|
</div>
|
||||||
</div>`,
|
</div>`,
|
||||||
|
|
||||||
onCreate(instance) {
|
onCreate(tippyInstance) {
|
||||||
instance.popper.classList.add("user-tip");
|
// Used to set correct z-index property on root tippy element
|
||||||
|
tippyInstance.popper.classList.add("user-tip");
|
||||||
|
|
||||||
instance.popper
|
tippyInstance.popper
|
||||||
.querySelector(".btn-dismiss")
|
.querySelector(".btn-dismiss")
|
||||||
.addEventListener("click", (event) => {
|
.addEventListener("click", (event) => {
|
||||||
options.onDismiss();
|
options.onDismiss?.();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.popper
|
tippyInstance.popper
|
||||||
.querySelector(".btn-dismiss-all")
|
.querySelector(".btn-dismiss-all")
|
||||||
.addEventListener("click", (event) => {
|
.addEventListener("click", (event) => {
|
||||||
options.onDismissAll();
|
options.onDismissAll?.();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
showNextUserTip();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hideUserTip(userTipId) {
|
export function hideUserTip(userTipId, force = false) {
|
||||||
const instance = instances[userTipId];
|
// Tippy instances are not destroyed immediately because sometimes there
|
||||||
if (instance && !instance.state.isDestroyed) {
|
// user tip is recreated immediately. This happens when Ember components
|
||||||
instance.destroy();
|
// are re-rendered because a parent component has changed
|
||||||
}
|
|
||||||
delete instances[userTipId];
|
|
||||||
|
|
||||||
const index = queue.findIndex((userTip) => userTip.id === userTipId);
|
const instance = instancesMap[userTipId];
|
||||||
if (index > -1) {
|
if (!instance) {
|
||||||
queue.splice(index, 1);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
destroyInstance(instance);
|
||||||
|
delete instancesMap[userTipId];
|
||||||
|
showNextUserTip();
|
||||||
|
} else if (!instance.destroyTimeout) {
|
||||||
|
instance.destroyTimeout = setTimeout(() => {
|
||||||
|
destroyInstance(instancesMap[userTipId]);
|
||||||
|
delete instancesMap[userTipId];
|
||||||
|
showNextUserTip();
|
||||||
|
}, TIPPY_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hideAllUserTips() {
|
export function hideAllUserTips() {
|
||||||
Object.keys(instances).forEach(hideUserTip);
|
Object.keys(instancesMap).forEach((userTipId) => {
|
||||||
}
|
destroyInstance(instancesMap[userTipId]);
|
||||||
|
delete instancesMap[userTipId];
|
||||||
function addToQueue(options) {
|
});
|
||||||
for (let i = 0; i < queue.size; ++i) {
|
|
||||||
if (queue[i].id === options.id) {
|
|
||||||
queue[i] = options;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.push(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showNextUserTip() {
|
export function showNextUserTip() {
|
||||||
const options = queue.shift();
|
const instances = Object.values(instancesMap);
|
||||||
if (options) {
|
|
||||||
showUserTip(options);
|
// Return early if a user tip is already visible and it is in viewport
|
||||||
|
if (
|
||||||
|
instances.find(
|
||||||
|
(instance) =>
|
||||||
|
instance.state.isVisible && isElementInViewport(instance.reference)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, try to find a user tip in the viewport
|
||||||
|
const idx = instances.findIndex((instance) =>
|
||||||
|
isElementInViewport(instance.reference)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If no instance was found, select first user tip
|
||||||
|
const newInstance = instances[idx === -1 ? 0 : idx];
|
||||||
|
|
||||||
|
// Show only selected instance and hide all the other ones
|
||||||
|
instances.forEach((instance) => {
|
||||||
|
if (instance === newInstance) {
|
||||||
|
showInstance(instance);
|
||||||
|
} else {
|
||||||
|
hideInstance(instance);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1172,33 +1172,42 @@ const User = RestModel.extend({
|
|||||||
return [...trackedTags, ...watchedTags, ...watchingFirstPostTags];
|
return [...trackedTags, ...watchedTags, ...watchingFirstPostTags];
|
||||||
},
|
},
|
||||||
|
|
||||||
showUserTip(options) {
|
canSeeUserTip(id) {
|
||||||
const userTips = Site.currentProp("user_tips");
|
const userTips = Site.currentProp("user_tips");
|
||||||
if (!userTips || this.user_option?.skip_new_user_tips) {
|
if (!userTips || this.user_option?.skip_new_user_tips) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userTips[options.id]) {
|
if (!userTips[id]) {
|
||||||
if (!isTesting()) {
|
if (!isTesting()) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn("Cannot show user tip with type =", options.id);
|
console.warn("Cannot show user tip with type =", id);
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const seenUserTips = this.user_option?.seen_popups || [];
|
const seenUserTips = this.user_option?.seen_popups || [];
|
||||||
if (
|
if (seenUserTips.includes(-1) || seenUserTips.includes(userTips[id])) {
|
||||||
seenUserTips.includes(-1) ||
|
return false;
|
||||||
seenUserTips.includes(userTips[options.id])
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showUserTip({
|
return true;
|
||||||
...options,
|
},
|
||||||
onDismiss: () => this.hideUserTipForever(options.id),
|
|
||||||
onDismissAll: () => this.hideUserTipForever(),
|
showUserTip(options) {
|
||||||
});
|
if (this.canSeeUserTip(options.id)) {
|
||||||
|
showUserTip({
|
||||||
|
...options,
|
||||||
|
onDismiss: () => {
|
||||||
|
options.onDismiss?.();
|
||||||
|
this.hideUserTipForever(options.id);
|
||||||
|
},
|
||||||
|
onDismissAll: () => {
|
||||||
|
options.onDismissAll?.();
|
||||||
|
this.hideUserTipForever();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hideUserTipForever(userTipId) {
|
hideUserTipForever(userTipId) {
|
||||||
@ -1216,7 +1225,7 @@ const User = RestModel.extend({
|
|||||||
|
|
||||||
// Hide user tips and maybe show the next one.
|
// Hide user tips and maybe show the next one.
|
||||||
if (userTipId) {
|
if (userTipId) {
|
||||||
hideUserTip(userTipId);
|
hideUserTip(userTipId, true);
|
||||||
showNextUserTip();
|
showNextUserTip();
|
||||||
} else {
|
} else {
|
||||||
hideAllUserTips();
|
hideAllUserTips();
|
||||||
|
@ -12,9 +12,11 @@ acceptance("User Tips - first_notification", function (needs) {
|
|||||||
needs.hooks.afterEach(() => hideAllUserTips());
|
needs.hooks.afterEach(() => hideAllUserTips());
|
||||||
|
|
||||||
test("Shows first notification user tip", async function (assert) {
|
test("Shows first notification user tip", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_tips = true;
|
||||||
|
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query(".user-tip-title").textContent.trim(),
|
query(".user-tip__title").textContent.trim(),
|
||||||
I18n.t("user_tips.first_notification.title")
|
I18n.t("user_tips.first_notification.title")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -32,7 +34,7 @@ acceptance("User Tips - topic_timeline", function (needs) {
|
|||||||
|
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query(".user-tip-title").textContent.trim(),
|
query(".user-tip__title").textContent.trim(),
|
||||||
I18n.t("user_tips.topic_timeline.title")
|
I18n.t("user_tips.topic_timeline.title")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -50,7 +52,7 @@ acceptance("User Tips - post_menu", function (needs) {
|
|||||||
|
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query(".user-tip-title").textContent.trim(),
|
query(".user-tip__title").textContent.trim(),
|
||||||
I18n.t("user_tips.post_menu.title")
|
I18n.t("user_tips.post_menu.title")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -63,13 +65,13 @@ acceptance("User Tips - topic_notification_levels", function (needs) {
|
|||||||
needs.hooks.beforeEach(() => hideAllUserTips());
|
needs.hooks.beforeEach(() => hideAllUserTips());
|
||||||
needs.hooks.afterEach(() => hideAllUserTips());
|
needs.hooks.afterEach(() => hideAllUserTips());
|
||||||
|
|
||||||
test("Shows post menu user tip", async function (assert) {
|
test("Shows topic notification levels user tip", async function (assert) {
|
||||||
this.siteSettings.enable_user_tips = true;
|
this.siteSettings.enable_user_tips = true;
|
||||||
|
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query(".user-tip-title").textContent.trim(),
|
query(".user-tip__title").textContent.trim(),
|
||||||
I18n.t("user_tips.topic_notification_levels.title")
|
I18n.t("user_tips.topic_notification_levels.title")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -82,12 +84,12 @@ acceptance("User Tips - suggested_topics", function (needs) {
|
|||||||
needs.hooks.beforeEach(() => hideAllUserTips());
|
needs.hooks.beforeEach(() => hideAllUserTips());
|
||||||
needs.hooks.afterEach(() => hideAllUserTips());
|
needs.hooks.afterEach(() => hideAllUserTips());
|
||||||
|
|
||||||
test("Shows post menu user tip", async function (assert) {
|
test("Shows suggested topics user tip", async function (assert) {
|
||||||
this.siteSettings.enable_user_tips = true;
|
this.siteSettings.enable_user_tips = true;
|
||||||
|
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query(".user-tip-title").textContent.trim(),
|
query(".user-tip__title").textContent.trim(),
|
||||||
I18n.t("user_tips.suggested_topics.title")
|
I18n.t("user_tips.suggested_topics.title")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -454,4 +454,9 @@ $mobile-avatar-height: 1.532em;
|
|||||||
font-size: var(--font-down-1);
|
font-size: var(--font-down-1);
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--primary-medium);
|
||||||
|
color: var(--secondary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.tippy-box[data-theme="user-tips"] {
|
.tippy-box[data-theme="user-tip"] {
|
||||||
background-color: var(--tertiary);
|
background-color: var(--tertiary);
|
||||||
color: var(--secondary);
|
color: var(--secondary);
|
||||||
border-color: var(--tertiary);
|
border-color: var(--tertiary);
|
||||||
@ -27,24 +27,24 @@
|
|||||||
|
|
||||||
.user-tip {
|
.user-tip {
|
||||||
z-index: z("composer", "content") - 1;
|
z-index: z("composer", "content") - 1;
|
||||||
}
|
|
||||||
|
|
||||||
.user-tip-container {
|
&__container {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.user-tip-title {
|
&__title {
|
||||||
font-size: $font-up-2;
|
font-size: var(--font-up-2);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-tip-content {
|
&__content {
|
||||||
margin-top: 0.25em;
|
margin-top: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-tip-buttons {
|
&__buttons {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,6 +356,7 @@ class User < ActiveRecord::Base
|
|||||||
post_menu: 3,
|
post_menu: 3,
|
||||||
topic_notification_levels: 4,
|
topic_notification_levels: 4,
|
||||||
suggested_topics: 5,
|
suggested_topics: 5,
|
||||||
|
admin_guide: 6,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ en:
|
|||||||
message: "We've updated this site, <span>please refresh</span>, or you may experience unexpected behavior."
|
message: "We've updated this site, <span>please refresh</span>, or you may experience unexpected behavior."
|
||||||
dismiss: "Dismiss"
|
dismiss: "Dismiss"
|
||||||
|
|
||||||
bootstrap_mode: "Bootstrap mode"
|
bootstrap_mode: "Getting started"
|
||||||
|
|
||||||
themes:
|
themes:
|
||||||
default_description: "Default"
|
default_description: "Default"
|
||||||
@ -1932,6 +1932,11 @@ en:
|
|||||||
title: "Keep reading!"
|
title: "Keep reading!"
|
||||||
content: "Here are some topics we think you might like to read next."
|
content: "Here are some topics we think you might like to read next."
|
||||||
|
|
||||||
|
admin_guide:
|
||||||
|
title: "Welcome to your new site!"
|
||||||
|
content: "Read the admin guide to continue building your site and community."
|
||||||
|
primary: "Let's go!"
|
||||||
|
|
||||||
loading: "Loading..."
|
loading: "Loading..."
|
||||||
errors:
|
errors:
|
||||||
prev_page: "while trying to load"
|
prev_page: "while trying to load"
|
||||||
|
@ -699,7 +699,7 @@ en:
|
|||||||
|
|
||||||
> If you need help or have a suggestion, feel free to ask in [#feedback](%{base_path}/c/site-feedback) or [contact the admins](%{base_path}/about).
|
> If you need help or have a suggestion, feel free to ask in [#feedback](%{base_path}/c/site-feedback) or [contact the admins](%{base_path}/about).
|
||||||
|
|
||||||
admin_quick_start_title: "READ ME FIRST: Admin Quick Start Guide"
|
admin_quick_start_title: "Admin Guide: Getting Started"
|
||||||
|
|
||||||
category:
|
category:
|
||||||
topic_prefix: "About the %{category} category"
|
topic_prefix: "About the %{category} category"
|
||||||
|
@ -241,6 +241,9 @@
|
|||||||
},
|
},
|
||||||
"suggested_topics": {
|
"suggested_topics": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"admin_guide": {
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@ -248,7 +251,8 @@
|
|||||||
"topic_timeline",
|
"topic_timeline",
|
||||||
"post_menu",
|
"post_menu",
|
||||||
"topic_notification_levels",
|
"topic_notification_levels",
|
||||||
"suggested_topics"
|
"suggested_topics",
|
||||||
|
"admin_guide"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"groups": {
|
"groups": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user