DEV: Remove the legacy widget post menu code (#31211)

https://meta.discourse.org/t/341014
This commit is contained in:
Sérgio Saquetim
2025-04-01 12:03:58 -03:00
committed by GitHub
parent 4fb2fae0ed
commit 083082f328
23 changed files with 1425 additions and 4471 deletions

View File

@ -615,7 +615,6 @@ export default class PostMenu extends Component {
{{! this.collapsed is included in the check below because "Show More" button can be overriden to be always visible }} {{! this.collapsed is included in the check below because "Show More" button can be overriden to be always visible }}
class={{concatClass class={{concatClass
"post-controls" "post-controls"
"glimmer-post-menu"
(if (if
(and (and
(this.showMoreButton.shouldRender (this.showMoreButton.shouldRender

View File

@ -43,7 +43,7 @@ export default class PostMenuRepliesButton extends Component {
<template> <template>
<DButton <DButton
class="show-replies btn-icon-text" class="post-action-menu__show-replies show-replies btn-icon-text"
...attributes ...attributes
disabled={{this.disabled}} disabled={{this.disabled}}
@action={{@buttonActions.toggleReplies}} @action={{@buttonActions.toggleReplies}}

View File

@ -1,31 +0,0 @@
import { withSilencedDeprecations } from "discourse/lib/deprecated";
import { withPluginApi } from "discourse/lib/plugin-api";
import PostBookmarkManager from "discourse/lib/post-bookmark-manager";
export default {
name: "discourse-bookmark-menu",
initialize(container) {
const currentUser = container.lookup("service:current-user");
withPluginApi("0.10.1", (api) => {
if (currentUser) {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.replacePostMenuButton("bookmark", {
name: "bookmark-menu-shim",
shouldRender: () => true,
buildAttrs: (widget) => {
return {
post: widget.findAncestorModel(),
bookmarkManager: new PostBookmarkManager(
container,
widget.findAncestorModel()
),
};
},
});
});
}
});
},
};

View File

@ -139,12 +139,6 @@ import { setNotificationsLimit } from "discourse/routes/user-notifications";
import { addComposerSaveErrorCallback } from "discourse/services/composer"; import { addComposerSaveErrorCallback } from "discourse/services/composer";
import { addPostClassesCallback } from "discourse/widgets/post"; import { addPostClassesCallback } from "discourse/widgets/post";
import { addDecorator } from "discourse/widgets/post-cooked"; import { addDecorator } from "discourse/widgets/post-cooked";
import {
addButton,
apiExtraButtons,
removeButton,
replaceButton,
} from "discourse/widgets/post-menu";
import { import {
addPostSmallActionClassesCallback, addPostSmallActionClassesCallback,
addPostSmallActionIcon, addPostSmallActionIcon,
@ -199,24 +193,12 @@ const DEPRECATED_POST_STREAM_WIDGETS = [
"topic-post-visited-line", "topic-post-visited-line",
]; ];
const DEPRECATED_POST_MENU_WIDGETS = [
"post-menu",
"post-user-tip-shim",
"small-user-list",
];
const POST_STREAM_DEPRECATION_OPTIONS = { const POST_STREAM_DEPRECATION_OPTIONS = {
since: "v3.5.0.beta1-dev", since: "v3.5.0.beta1-dev",
id: "discourse.post-stream-widget-overrides", id: "discourse.post-stream-widget-overrides",
// url: "", // TODO (glimmer-post-stream) uncomment when the topic is created on meta // url: "", // TODO (glimmer-post-stream) uncomment when the topic is created on meta
}; };
const POST_MENU_DEPRECATION_OPTIONS = {
since: "v3.4.0.beta3-dev",
id: "discourse.post-menu-widget-overrides",
url: "https://meta.discourse.org/t/341014",
};
export const RAW_TOPIC_LIST_DEPRECATION_OPTIONS = { export const RAW_TOPIC_LIST_DEPRECATION_OPTIONS = {
since: "v3.4.0.beta4-dev", since: "v3.4.0.beta4-dev",
id: "discourse.hbr-topic-list-overrides", id: "discourse.hbr-topic-list-overrides",
@ -942,59 +924,13 @@ class PluginApi {
} }
/** /**
* Add a new button below a post with your plugin. * Decommissioned API
*
* The `callback` function will be called whenever the post menu is rendered,
* and if you return an object with the button details it will be rendered.
*
* Example:
*
* ```
* api.addPostMenuButton('coffee', () => {
* return {
* action: 'drinkCoffee',
* icon: 'mug-saucer',
* className: 'hot-coffee',
* title: 'coffee.title',
* position: 'first' // can be `first`, `last` or `second-last-hidden`
* };
* });
*
* ```
*
* action: may be a string or a function. If it is a string, a widget action
* will be triggered. If it is function, the function will be called.
*
* function will receive a single argument:
* {
* post:
* showFeedback:
* }
*
* showFeedback can be called to issue a visual feedback on button press.
* It gets a single argument with a localization key.
*
* Example:
*
* api.addPostMenuButton('coffee', () => {
* return {
* action: ({ post, showFeedback }) => {
* drinkCoffee(post);
* showFeedback('discourse_plugin.coffee.drink');
* },
* icon: 'mug-saucer',
* className: 'hot-coffee',
* }
* }
**/ **/
addPostMenuButton(name, callback) { addPostMenuButton() {
deprecated( // eslint-disable-next-line no-console
"`api.addPostMenuButton` has been deprecated. Use the value transformer `post-menu-buttons` instead.", console.error(
POST_MENU_DEPRECATION_OPTIONS "`api.addPostMenuButton`: This API was decommissioned. Use the value transformer `post-menu-buttons` instead."
); );
apiExtraButtons[name] = callback;
addButton(name, callback);
} }
/** /**
@ -1046,54 +982,23 @@ class PluginApi {
} }
/** /**
* Remove existing button below a post with your plugin. * Decommissioned API
*
* Example:
*
* ```
* api.removePostMenuButton('like');
* ```
*
* ```
* api.removePostMenuButton('like', (attrs, state, siteSettings, settings, currentUser) => {
* if (attrs.post_number === 1) {
* return true;
* }
* });
* ```
**/ **/
removePostMenuButton(name, callback) { removePostMenuButton() {
deprecated( // eslint-disable-next-line no-console
"`api.removePostMenuButton` has been deprecated. Use the value transformer `post-menu-buttons` instead.", console.error(
POST_MENU_DEPRECATION_OPTIONS "`api.removePostMenuButton`: This API was decommissioned. Use the value transformer `post-menu-buttons` instead."
); );
removeButton(name, callback);
} }
/** /**
* Replace an existing button with a widget * Decommissioned API
*
* Example:
* ```
* api.replacePostMenuButton("like", {
* name: "widget-name",
* buildAttrs: (widget) => {
* return { post: widget.findAncestorModel() };
* },
* shouldRender: (widget) => {
* const post = widget.findAncestorModel();
* return post.id === 1
* }
* });
**/ **/
replacePostMenuButton(name, widget) { replacePostMenuButton() {
deprecated( // eslint-disable-next-line no-console
"`api.replacePostMenuButton` has been deprecated. Use the value transformer `post-menu-buttons` instead.", console.error(
POST_MENU_DEPRECATION_OPTIONS "`api.replacePostMenuButton`: This API was decommissioned. Use the value transformer `post-menu-buttons` instead."
); );
replaceButton(name, widget);
} }
/** /**
@ -3538,14 +3443,6 @@ class PluginApi {
`The \`${widgetName}\` widget has been deprecated and \`api.${override}\` is no longer a supported override.`, `The \`${widgetName}\` widget has been deprecated and \`api.${override}\` is no longer a supported override.`,
POST_STREAM_DEPRECATION_OPTIONS POST_STREAM_DEPRECATION_OPTIONS
); );
return;
}
if (DEPRECATED_POST_MENU_WIDGETS.includes(widgetName)) {
deprecated(
`The ${widgetName} widget has been deprecated and ${override} is no longer a supported override.`,
POST_MENU_DEPRECATION_OPTIONS
);
} }
} }
} }

View File

@ -18,7 +18,6 @@ export const CRITICAL_DEPRECATIONS = [
"discourse.add-header-panel", "discourse.add-header-panel",
"discourse.header-widget-overrides", "discourse.header-widget-overrides",
"discourse.plugin-outlet-tag-name", "discourse.plugin-outlet-tag-name",
"discourse.post-menu-widget-overrides",
"discourse.add-flag-property", "discourse.add-flag-property",
"discourse.breadcrumbs.childCategories", "discourse.breadcrumbs.childCategories",
"discourse.breadcrumbs.firstCategory", "discourse.breadcrumbs.firstCategory",

View File

@ -1,977 +0,0 @@
import { next } from "@ember/runloop";
import { hbs } from "ember-cli-htmlbars";
import { Promise } from "rsvp";
import { h } from "virtual-dom";
import AdminPostMenu from "discourse/components/admin-post-menu";
import DeleteTopicDisallowedModal from "discourse/components/modal/delete-topic-disallowed";
import { smallUserAttrs } from "discourse/components/small-user-list";
import { formattedReminderTime } from "discourse/lib/bookmark";
import discourseLater from "discourse/lib/later";
import { recentlyCopied, showAlert } from "discourse/lib/post-action-feedback";
import {
NO_REMINDER_ICON,
WITH_REMINDER_ICON,
} from "discourse/models/bookmark";
import RenderGlimmer, {
registerWidgetShim,
} from "discourse/widgets/render-glimmer";
import { applyDecorators, createWidget } from "discourse/widgets/widget";
import { i18n } from "discourse-i18n";
const LIKE_ACTION = 2;
const VIBRATE_DURATION = 5;
const _builders = {};
export let apiExtraButtons = {};
let _extraButtons = {};
let _buttonsToRemoveCallbacks = {};
let _buttonsToReplace = {};
export function addButton(name, builder) {
_extraButtons[name] = builder;
}
export function resetPostMenuExtraButtons() {
for (const key of Object.keys(apiExtraButtons)) {
delete apiExtraButtons[key];
}
_extraButtons = {};
_buttonsToRemoveCallbacks = {};
_buttonsToReplace = {};
}
export function removeButton(name, callback) {
// 🏌️
_buttonsToRemoveCallbacks[name] ??= [];
_buttonsToRemoveCallbacks[name].push(callback || (() => true));
}
export function replaceButton(name, replaceWith) {
_buttonsToReplace[name] = replaceWith;
}
function registerButton(name, builder) {
_builders[name] = builder;
}
export function buildButton(name, widget) {
let { attrs, state, siteSettings, settings, currentUser } = widget;
// Return early if the button is supposed to be removed via the plugin API
if (
_buttonsToRemoveCallbacks[name] &&
_buttonsToRemoveCallbacks[name].some((c) =>
c(attrs, state, siteSettings, settings, currentUser)
)
) {
return;
}
// Look for a button replacement, build and return widget attrs if present
let replacement = _buttonsToReplace[name];
if (replacement && replacement?.shouldRender(widget)) {
return {
replaced: true,
name: replacement.name,
attrs: replacement.buildAttrs(widget),
};
}
let builder = _builders[name];
if (builder) {
let button = builder(attrs, state, siteSettings, settings, currentUser);
if (button && !button.id) {
button.id = name;
}
return button;
}
}
registerButton("read-count", (attrs, state) => {
if (attrs.showReadIndicator) {
const count = attrs.readCount;
if (count > 0) {
let ariaPressed = (state?.readers?.length > 0).toString();
return {
action: "toggleWhoRead",
title: "post.controls.read_indicator",
className: "button-count read-indicator",
contents: count,
iconRight: true,
addContainer: false,
translatedAriaLabel: i18n("post.sr_post_read_count_button", {
count,
}),
ariaPressed,
};
}
}
});
registerButton("read", (attrs) => {
const readBySomeone = attrs.readCount > 0;
if (attrs.showReadIndicator && readBySomeone) {
return {
action: "toggleWhoRead",
title: "post.controls.read_indicator",
icon: "book-open-reader",
before: "read-count",
addContainer: false,
};
}
});
function likeCount(attrs, state) {
const count = attrs.likeCount;
if (count > 0) {
const title = attrs.liked
? count === 1
? "post.has_likes_title_only_you"
: "post.has_likes_title_you"
: "post.has_likes_title";
let icon = attrs.yours ? "d-liked" : "";
let addContainer = attrs.yours;
const additionalClass = attrs.yours ? "my-likes" : "regular-likes";
if (!attrs.showLike) {
icon = attrs.yours ? "d-liked" : "d-unliked";
addContainer = true;
}
let ariaPressed = (state?.likedUsers?.length > 0).toString();
return {
action: "toggleWhoLiked",
title,
className: `button-count like-count highlight-action ${additionalClass}`,
contents: count,
icon,
iconRight: true,
addContainer,
titleOptions: { count: attrs.liked ? count - 1 : count },
translatedAriaLabel: i18n("post.sr_post_like_count_button", { count }),
ariaPressed,
};
}
}
registerButton("like-count", likeCount);
registerButton(
"like",
(attrs, _state, _siteSettings, _settings, currentUser) => {
if (!attrs.showLike) {
return likeCount(attrs);
}
const className = attrs.liked ? "toggle-like has-like" : "toggle-like like";
const button = {
action: "like",
icon: attrs.liked ? "d-liked" : "d-unliked",
className,
before: "like-count",
data: {
"post-id": attrs.id,
},
};
// If the user has already liked the post and doesn't have permission
// to undo that operation, then indicate via the title that they've liked it
// and disable the button. Otherwise, set the title even if the user
// is anonymous (meaning they don't currently have permission to like);
// this is important for accessibility.
if (attrs.liked && !attrs.canToggleLike) {
button.title = "post.controls.has_liked";
} else {
button.title = attrs.liked
? "post.controls.undo_like"
: "post.controls.like";
}
if (currentUser && !attrs.canToggleLike) {
button.disabled = true;
}
return button;
}
);
registerButton("flag-count", (attrs) => {
let className = "button-count";
if (attrs.reviewableScorePendingCount > 0) {
className += " has-pending";
}
return {
className,
contents: h("span", attrs.reviewableScoreCount.toString()),
url: `/review/${attrs.reviewableId}`,
};
});
registerButton(
"flag",
(attrs, _state, siteSettings, _postMenuSettings, currentUser) => {
if (
attrs.reviewableId ||
(attrs.canFlag && !attrs.hidden) ||
(siteSettings.allow_all_users_to_flag_illegal_content && !currentUser)
) {
const button = {
action: "showFlags",
title: currentUser
? "post.controls.flag"
: "post.controls.anonymous_flag",
icon: "flag",
className: "create-flag",
};
if (attrs.reviewableId) {
button.before = "flag-count";
}
return button;
}
}
);
registerButton("edit", (attrs) => {
if (attrs.canEdit) {
return {
action: "editPost",
className: "edit",
title: "post.controls.edit",
icon: "pencil",
alwaysShowYours: true,
};
}
});
registerButton("reply-small", (attrs) => {
if (!attrs.canCreatePost) {
return;
}
const args = {
action: "replyToPost",
title: "post.controls.reply",
icon: "reply",
className: "reply",
translatedAriaLabel: i18n("post.sr_reply_to", {
post_number: attrs.post_number,
username: attrs.username,
}),
};
return args;
});
registerButton("wiki-edit", (attrs) => {
if (attrs.canEdit) {
const args = {
action: "editPost",
className: "edit create",
title: "post.controls.edit",
icon: "far-pen-to-square",
alwaysShowYours: true,
};
if (!attrs.mobileView) {
args.label = "post.controls.edit_action";
}
return args;
}
});
registerButton("replies", (attrs, state, siteSettings) => {
const count = attrs.replyCount;
if (!count) {
return;
}
let title;
let action = "toggleRepliesBelow";
let icon = state.repliesShown ? "chevron-up" : "chevron-down";
if (siteSettings.enable_filtered_replies_view) {
action = "toggleFilteredRepliesView";
icon = state.filteredRepliesShown ? "chevron-up" : "chevron-down";
title = state.filteredRepliesShown
? "post.view_all_posts"
: "post.filtered_replies_hint";
}
// Omit replies if the setting `suppress_reply_directly_below` is enabled
if (
count === 1 &&
attrs.replyDirectlyBelow &&
siteSettings.suppress_reply_directly_below
) {
return;
}
let ariaPressed, ariaExpanded;
if (!siteSettings.enable_filtered_replies_view) {
ariaPressed = state.repliesShown.toString();
ariaExpanded = state.repliesShown.toString();
}
return {
action,
icon,
className: "show-replies",
titleOptions: { count },
title,
labelOptions: { count },
label: attrs.mobileView ? "post.has_replies_count" : "post.has_replies",
iconRight: !siteSettings.enable_filtered_replies_view || attrs.mobileView,
disabled: !!attrs.deleted,
translatedAriaLabel: i18n("post.sr_expand_replies", { count }),
ariaExpanded,
ariaPressed,
ariaControls: `embedded-posts__bottom--${attrs.post_number}`,
};
});
registerButton("share", () => {
return {
action: "share",
icon: "d-post-share",
className: "share",
title: "post.controls.share",
};
});
registerButton("copyLink", () => {
return {
action: "copyLink",
icon: "d-post-share",
className: "post-action-menu__copy-link",
title: "post.controls.copy_title",
ariaLive: "polite",
};
});
registerButton("reply", (attrs, state, siteSettings, postMenuSettings) => {
const args = {
action: "replyToPost",
title: "post.controls.reply",
icon: "reply",
className: "reply create fade-out",
translatedAriaLabel: i18n("post.sr_reply_to", {
post_number: attrs.post_number,
username: attrs.username,
}),
};
if (!attrs.canCreatePost) {
return;
}
if (postMenuSettings.showReplyTitleOnMobile || !attrs.mobileView) {
args.label = "topic.reply.title";
}
return args;
});
registerButton(
"bookmark",
(attrs, _state, siteSettings, _settings, currentUser) => {
if (!attrs.canBookmark) {
return;
}
let classNames = ["bookmark"];
let title = "bookmarks.not_bookmarked";
let titleOptions = { name: "" };
if (attrs.bookmarked) {
classNames.push("bookmarked");
if (attrs.bookmarkReminderAt) {
classNames.push("with-reminder");
let formattedReminder = formattedReminderTime(
attrs.bookmarkReminderAt,
currentUser.user_option.timezone
);
title = "bookmarks.created_with_reminder";
titleOptions.date = formattedReminder;
} else {
title = "bookmarks.created";
}
if (attrs.bookmarkName) {
titleOptions.name = attrs.bookmarkName;
}
}
return {
id: attrs.bookmarked ? "unbookmark" : "bookmark",
action: "toggleBookmark",
title,
titleOptions,
className: classNames.join(" "),
icon: attrs.bookmarkReminderAt ? WITH_REMINDER_ICON : NO_REMINDER_ICON,
};
}
);
registerButton("admin", (attrs) => {
if (!attrs.canManage && !attrs.canWiki && !attrs.canEditStaffNotes) {
return;
}
return {
action: "openAdminMenu",
title: "post.controls.admin",
className: "show-post-admin-menu",
icon: "wrench",
sendActionEvent: true,
};
});
registerButton("delete", (attrs) => {
if (attrs.canRecoverTopic) {
return {
id: "recover_topic",
action: "recoverPost",
title: "topic.actions.recover",
icon: "arrow-rotate-left",
className: "recover",
};
} else if (attrs.canDeleteTopic) {
return {
id: "delete_topic",
action: "deletePost",
title: "post.controls.delete_topic",
icon: "trash-can",
className: "delete",
};
} else if (attrs.canRecover) {
return {
id: "recover",
action: "recoverPost",
title: "post.controls.undelete",
icon: "arrow-rotate-left",
className: "recover",
};
} else if (attrs.canDelete) {
return {
id: "delete",
action: "deletePost",
title: "post.controls.delete",
icon: "trash-can",
className: "delete",
};
} else if (attrs.showFlagDelete) {
return {
id: "delete_topic",
action: "showDeleteTopicModal",
title: "post.controls.delete_topic_disallowed",
icon: "trash-can",
className: "delete",
};
}
});
function _replaceButton(buttons, find, replace) {
const idx = buttons.indexOf(find);
if (idx !== -1) {
buttons[idx] = replace;
}
}
export default createWidget("post-menu", {
tagName: "section.post-menu-area.clearfix",
services: ["modal", "menu"],
settings: {
collapseButtons: true,
buttonType: "flat-button",
showReplyTitleOnMobile: false,
},
defaultState() {
return {
collapsed: true,
likedUsers: [],
readers: [],
};
},
buildKey: (attrs) => `post-menu-${attrs.id}`,
attachButton(name) {
let buttonAttrs = buildButton(name, this);
if (buttonAttrs?.component) {
return [
new RenderGlimmer(
this,
buttonAttrs.tagName,
hbs`<@data.component
@permanentlyDeletePost={{@data.permanentlyDeletePost}}
@lockPost={{@data.lockPost}}
@unlockPost={{@data.unlockPost}}
@grantBadge={{@data.grantBadge}}
@rebakePost={{@data.rebakePost}}
@toggleWiki={{@data.toggleWiki}}
@changePostOwner={{@data.changePostOwner}}
@changeNotice={{@data.changeNotice}}
@togglePostType={{@data.togglePostType}}
@unhidePost={{@data.unhidePost}}
@showPagePublish={{@data.showPagePublish}}
@post={{@data.post}}
@transformedPost={{@data.transformedPost}}
@scheduleRerender={{@data.scheduleRerender}}
/>`,
{
component: buttonAttrs.component,
transformedPost: this.attrs,
post: this.findAncestorModel(),
permanentlyDeletePost: () =>
this.sendWidgetAction("permanentlyDeletePost"),
lockPost: () => this.sendWidgetAction("lockPost"),
unlockPost: () => this.sendWidgetAction("unlockPost"),
grantBadge: () => this.sendWidgetAction("grantBadge"),
rebakePost: () => this.sendWidgetAction("rebakePost"),
toggleWiki: () => this.sendWidgetAction("toggleWiki"),
changePostOwner: () => this.sendWidgetAction("changePostOwner"),
changeNotice: () => this.sendWidgetAction("changeNotice"),
togglePostType: () => this.sendWidgetAction("togglePostType"),
scheduleRerender: () => this.scheduleRerender(),
}
),
];
}
// If the button is replaced via the plugin API, we need to render the
// replacement rather than a button
if (buttonAttrs?.replaced) {
return this.attach(buttonAttrs.name, buttonAttrs.attrs);
}
if (buttonAttrs) {
let button = this.attach(this.settings.buttonType, buttonAttrs);
if (buttonAttrs.before) {
let before = this.attachButton(buttonAttrs.before);
return h("div.double-button", [before, button]);
} else if (buttonAttrs.addContainer) {
return h("div.double-button", [button]);
}
return button;
}
},
menuItems() {
return this.siteSettings.post_menu.split("|").filter(Boolean);
},
html(attrs, state) {
const { currentUser, keyValueStore, siteSettings } = this;
const hiddenSetting = siteSettings.post_menu_hidden_items || "";
const hiddenButtons = hiddenSetting
.split("|")
.filter((s) => !attrs.bookmarked || s !== "bookmark");
if (currentUser && keyValueStore) {
const likedPostId = keyValueStore.getInt("likedPostId");
if (likedPostId === attrs.id) {
keyValueStore.remove("likedPostId");
next(() => this.sendWidgetAction("toggleLike"));
}
}
const allButtons = [];
let visibleButtons = [];
// filter menu items based on site settings
const orderedButtons = this.menuItems();
// If the post is a wiki, make Edit more prominent
if (attrs.wiki && attrs.canEdit) {
_replaceButton(orderedButtons, "edit", "reply-small");
_replaceButton(orderedButtons, "reply", "wiki-edit");
}
orderedButtons.forEach((i) => {
const button = this.attachButton(i, attrs);
if (button) {
allButtons.push(button);
if (
(attrs.yours && button.attrs && button.attrs.alwaysShowYours) ||
(attrs.reviewableId && i === "flag") ||
!hiddenButtons.includes(i)
) {
visibleButtons.push(button);
}
}
});
if (!this.settings.collapseButtons) {
visibleButtons = allButtons;
}
let hasShowMoreButton = false;
// Only show ellipsis if there is more than one button hidden
// if there are no more buttons, we are not collapsed
if (!state.collapsed || allButtons.length <= visibleButtons.length + 1) {
visibleButtons = allButtons;
if (state.collapsed) {
state.collapsed = false;
}
} else {
const showMore = this.attach("flat-button", {
action: "showMoreActions",
title: "show_more",
className: "show-more-actions",
icon: "ellipsis",
});
visibleButtons.splice(visibleButtons.length - 1, 0, showMore);
hasShowMoreButton = true;
}
Object.values(_extraButtons).forEach((builder) => {
let shouldAddButton = true;
if (_buttonsToRemoveCallbacks[name]) {
shouldAddButton = !_buttonsToRemoveCallbacks[name].some((c) =>
c(
attrs,
this.state,
this.siteSettings,
this.settings,
this.currentUser
)
);
}
if (shouldAddButton && builder) {
const buttonAttrs = builder(
attrs,
this.state,
this.siteSettings,
this.settings,
this.currentUser
);
if (buttonAttrs) {
const { position, beforeButton, afterButton } = buttonAttrs;
delete buttonAttrs.position;
let button;
if (typeof buttonAttrs.action === "function") {
const original = buttonAttrs.action;
const self = this;
buttonAttrs.action = async function (post) {
let showFeedback = null;
if (buttonAttrs.className) {
showFeedback = (messageKey) => {
showAlert(post.id, buttonAttrs.className, messageKey);
};
}
const postAttrs = {
post,
showFeedback,
};
if (
!buttonAttrs.className ||
!recentlyCopied(post.id, buttonAttrs.actionClass)
) {
self.sendWidgetAction(original, postAttrs);
}
};
}
button = this.attach(this.settings.buttonType, buttonAttrs);
const content = [];
if (beforeButton) {
content.push(beforeButton(h));
}
content.push(button);
if (afterButton) {
content.push(afterButton(h));
}
button = h("span.extra-buttons", content);
if (button) {
switch (position) {
case "first":
visibleButtons.unshift(button);
break;
case "second":
visibleButtons.splice(1, 0, button);
break;
case "second-last-hidden":
if (!state.collapsed) {
visibleButtons.splice(visibleButtons.length - 2, 0, button);
}
break;
default:
visibleButtons.push(button);
break;
}
}
}
}
});
const postControls = [];
const repliesButton = this.attachButton("replies", attrs);
if (repliesButton) {
postControls.push(repliesButton);
}
const extraPostControls = applyDecorators(
this,
"extra-post-controls",
attrs,
state
);
postControls.push(extraPostControls);
const extraControls = applyDecorators(this, "extra-controls", attrs, state);
const beforeExtraControls = applyDecorators(
this,
"before-extra-controls",
attrs,
state
);
const controlsButtons = [
...beforeExtraControls,
...visibleButtons,
...extraControls,
];
postControls.push(h("div.actions", controlsButtons));
const contents = [
h(
"nav.post-controls" +
(this.state.collapsed ? ".collapsed" : ".expanded") +
(siteSettings.enable_filtered_replies_view
? ".replies-button-visible"
: ""),
postControls
),
];
if (state.readers.length) {
const remaining = state.totalReaders - state.readers.length;
const description =
remaining > 0
? "post.actions.people.read_capped"
: "post.actions.people.read";
const count = remaining > 0 ? remaining : state.totalReaders;
contents.push(
this.attach("small-user-list", {
users: state.readers,
addSelf: false,
listClassName: "who-read",
description,
count,
isVisible: true,
})
);
}
if (state.likedUsers.length) {
const remaining = state.total - state.likedUsers.length;
const description =
remaining > 0
? "post.actions.people.like_capped"
: "post.actions.people.like";
const count = remaining > 0 ? remaining : state.total;
contents.push(
this.attach("small-user-list", {
users: state.likedUsers,
addSelf: attrs.liked && remaining === 0,
listClassName: "who-liked",
description,
count,
isVisible: true,
})
);
}
if (hasShowMoreButton) {
contents.push(this.attach("post-user-tip-shim"));
}
return contents;
},
openAdminMenu(event) {
this.menu.show(event.target, {
identifier: "admin-post-menu",
component: AdminPostMenu,
modalForMobile: true,
autofocus: true,
data: {
scheduleRerender: this.scheduleRerender.bind(this),
transformedPost: this.attrs,
post: this.findAncestorModel(),
permanentlyDeletePost: () =>
this.sendWidgetAction("permanentlyDeletePost"),
lockPost: () => this.sendWidgetAction("lockPost"),
unlockPost: () => this.sendWidgetAction("unlockPost"),
grantBadge: () => this.sendWidgetAction("grantBadge"),
rebakePost: () => this.sendWidgetAction("rebakePost"),
toggleWiki: () => this.sendWidgetAction("toggleWiki"),
changePostOwner: () => this.sendWidgetAction("changePostOwner"),
changeNotice: () => this.sendWidgetAction("changeNotice"),
togglePostType: () => this.sendWidgetAction("togglePostType"),
unhidePost: () => this.sendWidgetAction("unhidePost"),
showPagePublish: () => this.sendWidgetAction("showPagePublish"),
},
});
},
showDeleteTopicModal() {
this.modal.show(DeleteTopicDisallowedModal);
},
showMoreActions() {
this.state.collapsed = false;
const likesPromise = !this.state.likedUsers.length
? this.getWhoLiked()
: Promise.resolve();
return likesPromise.then(() => {
if (!this.state.readers.length && this.attrs.showReadIndicator) {
return this.getWhoRead();
}
});
},
like() {
const { attrs, currentUser, keyValueStore } = this;
if (!currentUser) {
keyValueStore &&
keyValueStore.set({ key: "likedPostId", value: attrs.id });
return this.sendWidgetAction("showLogin");
}
if (this.capabilities.userHasBeenActive && this.capabilities.canVibrate) {
navigator.vibrate(VIBRATE_DURATION);
}
if (attrs.liked) {
return this.sendWidgetAction("toggleLike");
}
const heart = document.querySelector(
`.toggle-like[data-post-id="${attrs.id}"] .d-icon`
);
heart.closest(".toggle-like").classList.add("has-like");
heart.classList.add("heart-animation");
return new Promise((resolve) => {
discourseLater(() => {
this.sendWidgetAction("toggleLike").then(() => resolve());
}, 400);
});
},
refreshLikes() {
if (this.state.likedUsers.length) {
return this.getWhoLiked();
}
},
refreshReaders() {
if (this.state.readers.length) {
return this.getWhoRead();
}
},
getWhoLiked() {
const { attrs, state } = this;
return this.store
.find("post-action-user", {
id: attrs.id,
post_action_type_id: LIKE_ACTION,
})
.then((users) => {
state.likedUsers = users.map(smallUserAttrs);
state.total = users.totalRows;
});
},
getWhoRead() {
const { attrs, state } = this;
return this.store.find("post-reader", { id: attrs.id }).then((users) => {
state.readers = users.map(smallUserAttrs);
state.totalReaders = users.totalRows;
});
},
toggleWhoLiked() {
const state = this.state;
if (state.likedUsers.length) {
state.likedUsers = [];
} else {
return this.getWhoLiked();
}
},
toggleWhoRead() {
const state = this.state;
if (this.state.readers.length) {
state.readers = [];
} else {
return this.getWhoRead();
}
},
});
// TODO (glimmer-post-menu): Once this widget is removed the `<section>...</section>` tag needs to be added to the PostMenu component
registerWidgetShim(
"glimmer-post-menu",
"section.post-menu-area.clearfix",
hbs`
<Post::Menu
@canCreatePost={{@data.canCreatePost}}
@filteredRepliesView={{@data.filteredRepliesView}}
@nextPost={{@data.nextPost}}
@post={{@data.post}}
@prevPost={{@data.prevPost}}
@repliesShown={{@data.repliesShown}}
@showReadIndicator={{@data.showReadIndicator}}
@changeNotice={{@data.changeNotice}}
@changePostOwner={{@data.changePostOwner}}
@copyLink={{@data.copyLink}}
@deletePost={{@data.deletePost}}
@editPost={{@data.editPost}}
@grantBadge={{@data.grantBadge}}
@lockPost={{@data.lockPost}}
@permanentlyDeletePost={{@data.permanentlyDeletePost}}
@rebakePost={{@data.rebakePost}}
@recoverPost={{@data.recoverPost}}
@replyToPost={{@data.replyToPost}}
@share={{@data.share}}
@showFlags={{@data.showFlags}}
@showLogin={{@data.showLogin}}
@showPagePublish={{@data.showPagePublish}}
@toggleLike={{@data.toggleLike}}
@togglePostType={{@data.togglePostType}}
@toggleReplies={{@data.toggleReplies}}
@toggleWiki={{@data.toggleWiki}}
@unhidePost={{@data.unhidePost}}
@unlockPost={{@data.unlockPost}}
/>`
);

View File

@ -6,7 +6,6 @@ import ShareTopicModal from "discourse/components/modal/share-topic";
import { dateNode } from "discourse/helpers/node"; import { dateNode } from "discourse/helpers/node";
import autoGroupFlairForUser from "discourse/lib/avatar-flair"; import autoGroupFlairForUser from "discourse/lib/avatar-flair";
import { avatarUrl, translateSize } from "discourse/lib/avatar-utils"; import { avatarUrl, translateSize } from "discourse/lib/avatar-utils";
import { registerDeprecationHandler } from "discourse/lib/deprecated";
import { isTesting } from "discourse/lib/environment"; import { isTesting } from "discourse/lib/environment";
import { relativeAgeMediumSpan } from "discourse/lib/formatter"; import { relativeAgeMediumSpan } from "discourse/lib/formatter";
import getURL, { getAbsoluteURL, getURLWithCDN } from "discourse/lib/get-url"; import getURL, { getAbsoluteURL, getURLWithCDN } from "discourse/lib/get-url";
@ -17,7 +16,6 @@ import {
prioritizeNameFallback, prioritizeNameFallback,
prioritizeNameInUx, prioritizeNameInUx,
} from "discourse/lib/settings"; } from "discourse/lib/settings";
import { consolePrefix } from "discourse/lib/source-identifier";
import { transformBasicPost } from "discourse/lib/transform-post"; import { transformBasicPost } from "discourse/lib/transform-post";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import { import {
@ -46,19 +44,6 @@ function transformWithCallbacks(post, topicUrl, store) {
return transformed; return transformed;
} }
let postMenuWidgetExtensionsAdded = null;
let postMenuConsoleWarningLogged = false;
registerDeprecationHandler((_, opts) => {
if (opts?.id === "discourse.post-menu-widget-overrides") {
if (!postMenuWidgetExtensionsAdded) {
postMenuWidgetExtensionsAdded = new Set();
}
postMenuWidgetExtensionsAdded.add(consolePrefix().slice(1, -1));
}
});
export function avatarImg(wanted, attrs) { export function avatarImg(wanted, attrs) {
const size = translateSize(wanted); const size = translateSize(wanted);
const url = avatarUrl(attrs.template, size); const url = avatarUrl(attrs.template, size);
@ -562,36 +547,45 @@ createWidget("post-contents", {
}, },
}; };
if ( const filteredRepliesView = this.siteSettings.enable_filtered_replies_view;
this.siteSettings.glimmer_post_menu_mode === "enabled" || result.push(
(this.siteSettings.glimmer_post_menu_mode === "auto" && // TODO (glimmer-post-stream):
!postMenuWidgetExtensionsAdded) // Once this widget shim is removed the `<section>...</section>` tag needs to be added to the PostMenu component
) { new RenderGlimmer(
if (!postMenuConsoleWarningLogged) { this,
postMenuConsoleWarningLogged = true; "section.post-menu-area.clearfix",
hbs`
if (!isTesting()) { <Post::Menu
// eslint-disable-next-line no-console @canCreatePost={{@data.canCreatePost}}
console.log("✅ Using the new 'glimmer' post menu!"); @filteredRepliesView={{@data.filteredRepliesView}}
} @nextPost={{@data.nextPost}}
@post={{@data.post}}
if (postMenuWidgetExtensionsAdded) { @prevPost={{@data.prevPost}}
// eslint-disable-next-line no-console @repliesShown={{@data.repliesShown}}
console.warn( @showReadIndicator={{@data.showReadIndicator}}
[ @changeNotice={{@data.changeNotice}}
"Using the new 'glimmer' post menu, even though there are themes and/or plugins using deprecated APIs (glimmer_post_menu_mode = enabled).\n" + @changePostOwner={{@data.changePostOwner}}
"The following plugins and/or themes are using deprecated APIs, their post menu customizations are broken and may cause your site to not work properly:", @copyLink={{@data.copyLink}}
...Array.from(postMenuWidgetExtensionsAdded).sort(), @deletePost={{@data.deletePost}}
// TODO (glimmer-post-menu): add link to meta topic here when the roadmap for the update is announced @editPost={{@data.editPost}}
].join("\n- ") @grantBadge={{@data.grantBadge}}
); @lockPost={{@data.lockPost}}
} @permanentlyDeletePost={{@data.permanentlyDeletePost}}
} @rebakePost={{@data.rebakePost}}
@recoverPost={{@data.recoverPost}}
const filteredRepliesView = @replyToPost={{@data.replyToPost}}
this.siteSettings.enable_filtered_replies_view; @share={{@data.share}}
result.push( @showFlags={{@data.showFlags}}
this.attach("glimmer-post-menu", { @showLogin={{@data.showLogin}}
@showPagePublish={{@data.showPagePublish}}
@toggleLike={{@data.toggleLike}}
@togglePostType={{@data.togglePostType}}
@toggleReplies={{@data.toggleReplies}}
@toggleWiki={{@data.toggleWiki}}
@unhidePost={{@data.unhidePost}}
@unlockPost={{@data.unlockPost}}
/>`,
{
canCreatePost: attrs.canCreatePost, canCreatePost: attrs.canCreatePost,
filteredRepliesView, filteredRepliesView,
nextPost: attrs.nextPost, nextPost: attrs.nextPost,
@ -626,27 +620,9 @@ createWidget("post-contents", {
toggleWiki: () => this.sendWidgetAction("toggleWiki"), // this action comes from the post stream toggleWiki: () => this.sendWidgetAction("toggleWiki"), // this action comes from the post stream
unhidePost: () => this.sendWidgetAction("unhidePost"), // this action comes from the post stream unhidePost: () => this.sendWidgetAction("unhidePost"), // this action comes from the post stream
unlockPost: () => this.sendWidgetAction("unlockPost"), // this action comes from the post stream unlockPost: () => this.sendWidgetAction("unlockPost"), // this action comes from the post stream
}) }
); )
} else { );
if (
this.siteSettings.glimmer_post_menu_mode !== "disabled" &&
postMenuWidgetExtensionsAdded &&
!postMenuConsoleWarningLogged
) {
postMenuConsoleWarningLogged = true;
// eslint-disable-next-line no-console
console.warn(
[
"Using the legacy 'widget' post menu because the following plugins and/or themes are using deprecated APIs:",
...Array.from(postMenuWidgetExtensionsAdded).sort(),
// TODO (glimmer-post-menu): add link to meta topic here when the roadmap for the update is announced
].join("\n- ")
);
}
result.push(this.attach("post-menu", attrs, extraState));
}
const repliesBelow = state.repliesBelow; const repliesBelow = state.repliesBelow;
if (repliesBelow.length) { if (repliesBelow.length) {
@ -1204,7 +1180,7 @@ export default createWidget("post", {
}, },
}); });
// TODO (glimmer-post-menu): Once this widget is removed the `<section>...</section>` tag needs to be added to the PostMenu component // TODO (glimmer-post-stream): Once this widget is removed the `<section>...</section>` tag needs to be added to the PostMenu component
registerWidgetShim( registerWidgetShim(
"glimmer-post", "glimmer-post",
"div", "div",

View File

@ -3,85 +3,76 @@ import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
["enabled", "disabled"].forEach((postMenuMode) => { acceptance(`Post controls`, function () {
acceptance( test("accessibility of the likes list below the post", async function (assert) {
`Post controls (glimmer_post_menu_mode = ${postMenuMode})`, await visit("/t/internationalization-localization/280");
function (needs) {
needs.settings({
glimmer_post_menu_mode: postMenuMode,
});
test("accessibility of the likes list below the post", async function (assert) { assert
await visit("/t/internationalization-localization/280"); .dom("#post_2 button.like-count")
.hasAria("pressed", "false", "show likes button isn't pressed");
assert await click("#post_2 button.like-count");
.dom("#post_2 button.like-count") assert
.hasAria("pressed", "false", "show likes button isn't pressed"); .dom("#post_2 button.like-count")
.hasAria("pressed", "true", "show likes button is now pressed");
await click("#post_2 button.like-count"); assert
assert .dom("#post_2 .small-user-list.who-liked .small-user-list-content")
.dom("#post_2 button.like-count") .hasAttribute("role", "list", "likes container has list role");
.hasAria("pressed", "true", "show likes button is now pressed");
assert assert
.dom("#post_2 .small-user-list.who-liked .small-user-list-content") .dom("#post_2 .small-user-list.who-liked a.trigger-user-card")
.hasAttribute("role", "list", "likes container has list role"); .exists("avatars are rendered");
assert assert
.dom("#post_2 .small-user-list.who-liked a.trigger-user-card") .dom("#post_2 .small-user-list.who-liked a.trigger-user-card")
.exists("avatars are rendered"); .hasAria("hidden", "false", "avatars are not aria-hidden");
assert
.dom("#post_2 .small-user-list.who-liked a.trigger-user-card")
.hasAttribute("role", "listitem", "avatars have listitem role");
});
assert test("accessibility of the embedded replies below the post", async function (assert) {
.dom("#post_2 .small-user-list.who-liked a.trigger-user-card") await visit("/t/internationalization-localization/280");
.hasAria("hidden", "false", "avatars are not aria-hidden");
assert
.dom("#post_2 .small-user-list.who-liked a.trigger-user-card")
.hasAttribute("role", "listitem", "avatars have listitem role");
});
test("accessibility of the embedded replies below the post", async function (assert) { assert
await visit("/t/internationalization-localization/280"); .dom("#post_1 button.show-replies")
.hasAria("pressed", "false", "show replies button isn't pressed");
assert
.dom("#post_1 button.show-replies")
.hasAria(
"label",
i18n("post.sr_expand_replies", { count: 1 }),
"show replies button has aria-label"
);
assert await click("#post_1 button.show-replies");
.dom("#post_1 button.show-replies") assert
.hasAria("pressed", "false", "show replies button isn't pressed"); .dom("#post_1 button.show-replies")
assert .hasAria("pressed", "true", "show replies button is now pressed");
.dom("#post_1 button.show-replies")
.hasAria(
"label",
i18n("post.sr_expand_replies", { count: 1 }),
"show replies button has aria-label"
);
await click("#post_1 button.show-replies"); // const replies = Array.from(queryAll("#post_1 .embedded-posts .reply"));
assert assert
.dom("#post_1 button.show-replies") .dom("#post_1 .embedded-posts .reply")
.hasAria("pressed", "true", "show replies button is now pressed"); .exists({ count: 1 }, "replies are rendered");
// const replies = Array.from(queryAll("#post_1 .embedded-posts .reply")); assert
assert .dom("#post_1 .embedded-posts .reply")
.dom("#post_1 .embedded-posts .reply") .hasAttribute("role", "region", "replies have region role");
.exists({ count: 1 }, "replies are rendered"); assert.dom("#post_1 .embedded-posts .reply").hasAria(
"label",
assert i18n("post.sr_embedded_reply_description", {
.dom("#post_1 .embedded-posts .reply") post_number: 1,
.hasAttribute("role", "region", "replies have region role"); username: "somebody",
assert.dom("#post_1 .embedded-posts .reply").hasAria( }),
"label", "replies have aria-label"
i18n("post.sr_embedded_reply_description", { );
post_number: 1, assert
username: "somebody", .dom("#post_1 .embedded-posts .btn.collapse-up")
}), .hasAria(
"replies have aria-label" "label",
); i18n("post.sr_collapse_replies"),
assert "collapse button has aria-label"
.dom("#post_1 .embedded-posts .btn.collapse-up") );
.hasAria( });
"label",
i18n("post.sr_collapse_replies"),
"collapse button has aria-label"
);
});
}
);
}); });

View File

@ -19,254 +19,248 @@ import selectKit from "discourse/tests/helpers/select-kit-helper";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
["enabled", "disabled"].forEach((postStreamMode) => { ["enabled", "disabled"].forEach((postStreamMode) => {
["enabled", "disabled"].forEach((postMenuMode) => { acceptance(
acceptance( `Topic (glimmer_post_stream_mode = ${postStreamMode})`,
`Topic (glimmer_post_stream_mode = ${postStreamMode}) (glimmer_post_menu_mode = ${postMenuMode})`, function (needs) {
function (needs) { needs.user();
needs.user(); needs.settings({
needs.settings({ post_menu:
post_menu: "read|like|share|flag|edit|bookmark|delete|admin|reply|copyLink",
"read|like|share|flag|edit|bookmark|delete|admin|reply|copyLink", glimmer_post_stream_mode: postStreamMode,
glimmer_post_menu_mode: postMenuMode, });
glimmer_post_stream_mode: postStreamMode, needs.pretender((server, helper) => {
server.get("/c/2/visible_groups.json", () =>
helper.response(200, {
groups: [],
})
);
server.get("/c/feature/find_by_slug.json", () => {
return helper.response(200, CategoryFixtures["/c/1/show.json"]);
}); });
needs.pretender((server, helper) => { server.put("/posts/398/wiki", () => {
server.get("/c/2/visible_groups.json", () => return helper.response({});
helper.response(200, { });
groups: [], });
})
test("Reply as new topic", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("button.share:nth-of-type(1)");
await click("button.new-topic");
assert.dom(".d-editor-input").exists("the composer input is visible");
assert
.dom(".d-editor-input")
.hasValue(
`Continuing the discussion from [Internationalization / localization](${window.location.origin}/t/internationalization-localization/280):\n\n`,
"fills composer with the ring string"
);
assert.strictEqual(
selectKit(".category-chooser").header().value(),
"2",
"fills category selector with the right category"
);
});
test("Reply as new message", async function (assert) {
await visit("/t/pm-for-testing/12");
await click("button.share:nth-of-type(1)");
await click("button.new-topic");
assert.dom(".d-editor-input").exists("the composer input is visible");
assert
.dom(".d-editor-input")
.hasValue(
`Continuing the discussion from [PM for testing](${window.location.origin}/t/pm-for-testing/12):\n\n`,
"fills composer with the ring string"
); );
server.get("/c/feature/find_by_slug.json", () => { const privateMessageUsers = selectKit("#private-message-users");
return helper.response(200, CategoryFixtures["/c/1/show.json"]); assert.strictEqual(
}); privateMessageUsers.header().value(),
server.put("/posts/398/wiki", () => { "someguy,test,Group",
return helper.response({}); "fills up the composer correctly"
}); );
}); });
test("Reply as new topic", async function (assert) { test("Share Modal", async function (assert) {
await visit("/t/internationalization-localization/280"); await visit("/t/internationalization-localization/280");
await click("button.share:nth-of-type(1)"); await click(".topic-post:first-child button.share");
await click("button.new-topic");
assert.dom(".d-editor-input").exists("the composer input is visible"); assert.dom(".share-topic-modal").exists("shows the share modal");
});
assert test("Copy Link Button", async function (assert) {
.dom(".d-editor-input") await visit("/t/internationalization-localization/280");
.hasValue( await click(
`Continuing the discussion from [Internationalization / localization](${window.location.origin}/t/internationalization-localization/280):\n\n`, ".topic-post:first-child button.post-action-menu__copy-link"
"fills composer with the ring string" );
);
assert.strictEqual( assert
selectKit(".category-chooser").header().value(), .dom(".post-action-menu__copy-link-checkmark")
"2", .exists("shows the Link Copied! message");
"fills category selector with the right category" });
test("Showing and hiding the edit controls", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil");
assert.dom("#edit-title").exists("shows the editing controls");
assert
.dom(".title-wrapper .remove-featured-link")
.doesNotExist("link to remove featured link is not shown");
await fillIn("#edit-title", "this is the new title");
await click("#topic-title .cancel-edit");
assert.dom("#edit-title").doesNotExist("hides the editing controls");
});
test("Updating the topic title and category", async function (assert) {
const categoryChooser = selectKit(".title-wrapper .category-chooser");
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil");
await fillIn("#edit-title", "this is the new title");
await categoryChooser.expand();
await categoryChooser.selectRowByValue(4);
await click("#topic-title .submit-edit");
assert
.dom("#topic-title .badge-category")
.hasText("faq", "displays the new category");
assert
.dom(".fancy-title")
.hasText("this is the new title", "displays the new title");
});
test("Marking a topic as wiki", async function (assert) {
await visit("/t/internationalization-localization/280");
assert.dom("a.wiki").doesNotExist("does not show the wiki icon");
await click(".topic-post:nth-of-type(1) button.show-more-actions");
await click(".topic-post:nth-of-type(1) button.show-post-admin-menu");
await click(".btn.wiki");
assert.dom("button.wiki").exists("shows the wiki icon");
});
test("Visit topic routes", async function (assert) {
await visit("/t/12");
assert
.dom(".fancy-title")
.hasText("PM for testing", "routes to the right topic");
await visit("/t/280/20");
assert
.dom(".fancy-title")
.hasText(
"Internationalization / localization",
"routes to the right topic"
); );
}); });
test("Reply as new message", async function (assert) { test("Updating the topic title with emojis", async function (assert) {
await visit("/t/pm-for-testing/12"); await visit("/t/internationalization-localization/280");
await click("button.share:nth-of-type(1)"); await click("#topic-title .d-icon-pencil");
await click("button.new-topic");
assert.dom(".d-editor-input").exists("the composer input is visible"); await fillIn("#edit-title", "emojis title :bike: :blonde_woman:t6:");
assert await click("#topic-title .submit-edit");
.dom(".d-editor-input")
.hasValue(
`Continuing the discussion from [PM for testing](${window.location.origin}/t/pm-for-testing/12):\n\n`,
"fills composer with the ring string"
);
const privateMessageUsers = selectKit("#private-message-users"); assert
assert.strictEqual( .dom(".fancy-title")
privateMessageUsers.header().value(), .includesHtml("bike.png", "displays the new title with emojis");
"someguy,test,Group", });
"fills up the composer correctly"
test("Updating the topic title with unicode emojis", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil");
await fillIn("#edit-title", "emojis title 👨‍🌾🙏");
await click("#topic-title .submit-edit");
assert
.dom(".fancy-title")
.includesHtml("man_farmer.png", "displays the new title with emojis");
});
test("Updating the topic title with unicode emojis without whitespace", async function (assert) {
this.siteSettings.enable_inline_emoji_translation = true;
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil");
await fillIn("#edit-title", "Test🙂Title");
await click("#topic-title .submit-edit");
assert
.dom(".fancy-title")
.includesHtml(
"slightly_smiling_face.png",
"displays the new title with emojis"
); );
}); });
test("Share Modal", async function (assert) { test("Suggested topics", async function (assert) {
await visit("/t/internationalization-localization/280"); await visit("/t/internationalization-localization/280");
await click(".topic-post:first-child button.share");
assert.dom(".share-topic-modal").exists("shows the share modal"); assert
}); .dom("#suggested-topics-title")
.hasText(i18n("suggested_topics.title"));
});
test("Copy Link Button", async function (assert) { test("Deleting a topic", async function (assert) {
await visit("/t/internationalization-localization/280"); this.siteSettings.min_topic_views_for_delete_confirm = 10000;
await click( await visit("/t/internationalization-localization/280");
".topic-post:first-child button.post-action-menu__copy-link" await click(".topic-post:nth-of-type(1) button.show-more-actions");
); await click(".topic-post:nth-of-type(1) button.delete");
await click(".toggle-admin-menu");
assert.dom(".topic-admin-recover").exists("shows the recover button");
});
assert test("Deleting a popular topic displays confirmation modal", async function (assert) {
.dom(".post-action-menu__copy-link-checkmark") this.siteSettings.min_topic_views_for_delete_confirm = 10;
.exists("shows the Link Copied! message"); await visit("/t/internationalization-localization/280");
}); await click(".topic-post:nth-of-type(1) button.show-more-actions");
await click(".topic-post:nth-of-type(1) button.delete");
assert
.dom(".delete-topic-confirm-modal")
.exists("shows the delete confirmation modal");
test("Showing and hiding the edit controls", async function (assert) { await click(".delete-topic-confirm-modal .btn-primary");
await visit("/t/internationalization-localization/280"); assert
.dom(".delete-topic-confirm-modal")
.doesNotExist("hides the delete confirmation modal");
await click(".topic-post:nth-of-type(1) button.delete");
await click(".delete-topic-confirm-modal .btn-danger");
await click(".toggle-admin-menu");
assert.dom(".topic-admin-recover").exists("shows the recover button");
});
await click("#topic-title .d-icon-pencil"); test("Group category moderator posts", async function (assert) {
await visit("/t/topic-for-group-moderators/2480");
assert.dom("#edit-title").exists("shows the editing controls"); assert.dom(".category-moderator").exists("has a class applied");
assert assert.dom(".d-icon-shield-halved").exists("shows an icon");
.dom(".title-wrapper .remove-featured-link") });
.doesNotExist("link to remove featured link is not shown");
await fillIn("#edit-title", "this is the new title"); test("Suspended user posts", async function (assert) {
await click("#topic-title .cancel-edit"); await visit("/t/topic-from-suspended-user/54077");
assert.dom("#edit-title").doesNotExist("hides the editing controls");
});
test("Updating the topic title and category", async function (assert) { assert
const categoryChooser = selectKit(".title-wrapper .category-chooser"); .dom(".topic-post.user-suspended > #post_1")
.exists("has a class applied");
await visit("/t/internationalization-localization/280"); });
}
await click("#topic-title .d-icon-pencil"); );
await fillIn("#edit-title", "this is the new title");
await categoryChooser.expand();
await categoryChooser.selectRowByValue(4);
await click("#topic-title .submit-edit");
assert
.dom("#topic-title .badge-category")
.hasText("faq", "displays the new category");
assert
.dom(".fancy-title")
.hasText("this is the new title", "displays the new title");
});
test("Marking a topic as wiki", async function (assert) {
await visit("/t/internationalization-localization/280");
assert.dom("a.wiki").doesNotExist("does not show the wiki icon");
await click(".topic-post:nth-of-type(1) button.show-more-actions");
await click(".topic-post:nth-of-type(1) button.show-post-admin-menu");
await click(".btn.wiki");
assert.dom("button.wiki").exists("shows the wiki icon");
});
test("Visit topic routes", async function (assert) {
await visit("/t/12");
assert
.dom(".fancy-title")
.hasText("PM for testing", "routes to the right topic");
await visit("/t/280/20");
assert
.dom(".fancy-title")
.hasText(
"Internationalization / localization",
"routes to the right topic"
);
});
test("Updating the topic title with emojis", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil");
await fillIn("#edit-title", "emojis title :bike: :blonde_woman:t6:");
await click("#topic-title .submit-edit");
assert
.dom(".fancy-title")
.includesHtml("bike.png", "displays the new title with emojis");
});
test("Updating the topic title with unicode emojis", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil");
await fillIn("#edit-title", "emojis title 👨‍🌾🙏");
await click("#topic-title .submit-edit");
assert
.dom(".fancy-title")
.includesHtml(
"man_farmer.png",
"displays the new title with emojis"
);
});
test("Updating the topic title with unicode emojis without whitespace", async function (assert) {
this.siteSettings.enable_inline_emoji_translation = true;
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil");
await fillIn("#edit-title", "Test🙂Title");
await click("#topic-title .submit-edit");
assert
.dom(".fancy-title")
.includesHtml(
"slightly_smiling_face.png",
"displays the new title with emojis"
);
});
test("Suggested topics", async function (assert) {
await visit("/t/internationalization-localization/280");
assert
.dom("#suggested-topics-title")
.hasText(i18n("suggested_topics.title"));
});
test("Deleting a topic", async function (assert) {
this.siteSettings.min_topic_views_for_delete_confirm = 10000;
await visit("/t/internationalization-localization/280");
await click(".topic-post:nth-of-type(1) button.show-more-actions");
await click(".topic-post:nth-of-type(1) button.delete");
await click(".toggle-admin-menu");
assert.dom(".topic-admin-recover").exists("shows the recover button");
});
test("Deleting a popular topic displays confirmation modal", async function (assert) {
this.siteSettings.min_topic_views_for_delete_confirm = 10;
await visit("/t/internationalization-localization/280");
await click(".topic-post:nth-of-type(1) button.show-more-actions");
await click(".topic-post:nth-of-type(1) button.delete");
assert
.dom(".delete-topic-confirm-modal")
.exists("shows the delete confirmation modal");
await click(".delete-topic-confirm-modal .btn-primary");
assert
.dom(".delete-topic-confirm-modal")
.doesNotExist("hides the delete confirmation modal");
await click(".topic-post:nth-of-type(1) button.delete");
await click(".delete-topic-confirm-modal .btn-danger");
await click(".toggle-admin-menu");
assert.dom(".topic-admin-recover").exists("shows the recover button");
});
test("Group category moderator posts", async function (assert) {
await visit("/t/topic-for-group-moderators/2480");
assert.dom(".category-moderator").exists("has a class applied");
assert.dom(".d-icon-shield-halved").exists("shows an icon");
});
test("Suspended user posts", async function (assert) {
await visit("/t/topic-from-suspended-user/54077");
assert
.dom(".topic-post.user-suspended > #post_1")
.exists("has a class applied");
});
}
);
});
acceptance( acceptance(
`Topic featured links (glimmer_post_stream_mode = ${postStreamMode})`, `Topic featured links (glimmer_post_stream_mode = ${postStreamMode})`,

View File

@ -49,26 +49,16 @@ acceptance("User Tips - topic_timeline", function (needs) {
}); });
}); });
["enabled", "disabled"].forEach((postMenuMode) => { acceptance(`User Tips - post_menu`, function (needs) {
acceptance( needs.user();
`User Tips - post_menu (glimmer_post_menu_mode = ${postMenuMode})`, needs.site({ user_tips: { post_menu: 3 } });
function (needs) {
needs.user();
needs.site({ user_tips: { post_menu: 3 } });
needs.settings({
glimmer_post_menu_mode: postMenuMode,
});
test("Shows post menu user tip", async function (assert) { test("Shows post menu 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 assert.dom(".user-tip__title").hasText(i18n("user_tips.post_menu.title"));
.dom(".user-tip__title") });
.hasText(i18n("user_tips.post_menu.title"));
});
}
);
}); });
acceptance("User Tips - topic_notification_levels", function (needs) { acceptance("User Tips - topic_notification_levels", function (needs) {

View File

@ -102,7 +102,6 @@ import {
} from "discourse/tests/helpers/site-settings"; } from "discourse/tests/helpers/site-settings";
import { resetPostClassesCallback } from "discourse/widgets/post"; import { resetPostClassesCallback } from "discourse/widgets/post";
import { resetDecorators as resetPostCookedDecorators } from "discourse/widgets/post-cooked"; import { resetDecorators as resetPostCookedDecorators } from "discourse/widgets/post-cooked";
import { resetPostMenuExtraButtons } from "discourse/widgets/post-menu";
import { resetPostSmallActionClassesCallbacks } from "discourse/widgets/post-small-action"; import { resetPostSmallActionClassesCallbacks } from "discourse/widgets/post-small-action";
import { resetDecorators } from "discourse/widgets/widget"; import { resetDecorators } from "discourse/widgets/widget";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
@ -221,7 +220,6 @@ export function testCleanup(container, app) {
resetCardClickListenerSelector(); resetCardClickListenerSelector();
resetComposerCustomizations(); resetComposerCustomizations();
resetQuickSearchRandomTips(); resetQuickSearchRandomTips();
resetPostMenuExtraButtons();
resetUserMenuProfileTabItems(); resetUserMenuProfileTabItems();
clearExtraKeyboardShortcutHelp(); clearExtraKeyboardShortcutHelp();
clearDisabledDefaultKeyboardBindings(); clearDisabledDefaultKeyboardBindings();

View File

@ -51,7 +51,6 @@ module("Integration | Component | Post", function (hooks) {
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.siteSettings.glimmer_post_stream_mode = "enabled"; this.siteSettings.glimmer_post_stream_mode = "enabled";
this.siteSettings.glimmer_post_menu_mode = "enabled";
this.siteSettings.post_menu_hidden_items = ""; this.siteSettings.post_menu_hidden_items = "";
this.store = getOwner(this).lookup("service:store"); this.store = getOwner(this).lookup("service:store");

View File

@ -1,238 +0,0 @@
import { click, render } from "@ember/test-helpers";
import { module, test } from "qunit";
import { h } from "virtual-dom";
import MountWidget from "discourse/components/mount-widget";
import { withSilencedDeprecations } from "discourse/lib/deprecated";
import { withPluginApi } from "discourse/lib/plugin-api";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { resetPostMenuExtraButtons } from "discourse/widgets/post-menu";
import { createWidget } from "discourse/widgets/widget";
module("Integration | Component | Widget | post-menu", function (hooks) {
setupRenderingTest(hooks);
hooks.afterEach(function () {
resetPostMenuExtraButtons();
});
test("add extra button", async function (assert) {
const self = this;
this.set("args", {});
withPluginApi("0.14.0", (api) => {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.addPostMenuButton("coffee", () => {
return {
action: "drinkCoffee",
icon: "mug-saucer",
className: "hot-coffee",
title: "coffee.title",
position: "first",
};
});
});
});
await render(
<template>
<MountWidget @widget="post-menu" @args={{self.args}} />
</template>
);
assert
.dom(".actions .extra-buttons .hot-coffee")
.exists("renders extra button");
});
test("add extra button with feedback", async function (assert) {
const self = this;
this.set("args", {});
let testPost = null;
withPluginApi("0.14.0", (api) => {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.addPostMenuButton("coffee", () => {
return {
action: ({ post, showFeedback }) => {
testPost = post;
showFeedback("coffee.drink");
},
icon: "mug-saucer",
className: "hot-coffee",
title: "coffee.title",
position: "first",
actionParam: { id: 123 }, // hack for testing
};
});
});
});
await render(
<template>
<article data-post-id="123">
<MountWidget @widget="post-menu" @args={{self.args}} />
</article>
</template>
);
await click(".hot-coffee");
assert.strictEqual(testPost.id, 123, "callback was called with post");
assert.dom(".post-action-feedback-button").exists("renders feedback");
assert
.dom(".actions .extra-buttons .hot-coffee")
.exists("renders extra button");
});
test("removes button based on callback", async function (assert) {
const self = this;
this.set("args", { canCreatePost: true, canRemoveReply: true });
withPluginApi("0.14.0", (api) => {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.removePostMenuButton("reply", (attrs) => {
return attrs.canRemoveReply;
});
});
});
await render(
<template>
<MountWidget @widget="post-menu" @args={{self.args}} />
</template>
);
assert.dom(".actions .reply").doesNotExist("removes reply button");
});
test("does not remove button", async function (assert) {
const self = this;
this.set("args", { canCreatePost: true, canRemoveReply: false });
withPluginApi("0.14.0", (api) => {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.removePostMenuButton("reply", (attrs) => {
return attrs.canRemoveReply;
});
});
});
await render(
<template>
<MountWidget @widget="post-menu" @args={{self.args}} />
</template>
);
assert.dom(".actions .reply").exists("does not remove reply button");
});
test("removes button", async function (assert) {
const self = this;
this.set("args", { canCreatePost: true });
withPluginApi("0.14.0", (api) => {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.removePostMenuButton("reply");
});
});
await render(
<template>
<MountWidget @widget="post-menu" @args={{self.args}} />
</template>
);
assert.dom(".actions .reply").doesNotExist("removes reply button");
});
test("removes button when any callback evaluates to true", async function (assert) {
const self = this;
this.set("args", {});
withPluginApi("0.14.0", (api) => {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.removePostMenuButton("reply", () => true);
api.removePostMenuButton("reply", () => false);
});
});
await render(
<template>
<MountWidget @widget="post-menu" @args={{self.args}} />
</template>
);
assert.dom(".actions .reply").doesNotExist("removes reply button");
});
createWidget("post-menu-replacement", {
html(attrs) {
return h("h1.post-menu-replacement", {}, attrs.id);
},
});
test("buttons are replaced when shouldRender is true", async function (assert) {
const self = this;
this.set("args", { id: 1, canCreatePost: true });
withPluginApi("0.14.0", (api) => {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.replacePostMenuButton("reply", {
name: "post-menu-replacement",
buildAttrs: (widget) => {
return widget.attrs;
},
shouldRender: (widget) => widget.attrs.id === 1, // true!
});
});
});
await render(
<template>
<MountWidget @widget="post-menu" @args={{self.args}} />
</template>
);
assert.dom("h1.post-menu-replacement").exists("replacement is rendered");
assert
.dom(".actions .reply")
.doesNotExist("reply button is replaced button");
});
test("buttons are not replaced when shouldRender is false", async function (assert) {
const self = this;
this.set("args", { id: 1, canCreatePost: true, canRemoveReply: false });
withPluginApi("0.14.0", (api) => {
withSilencedDeprecations("discourse.post-menu-widget-overrides", () => {
api.replacePostMenuButton("reply", {
name: "post-menu-replacement",
buildAttrs: (widget) => {
return widget.attrs;
},
shouldRender: (widget) => widget.attrs.id === 102323948, // false!
});
});
});
await render(
<template>
<MountWidget @widget="post-menu" @args={{self.args}} />
</template>
);
assert
.dom("h1.post-menu-replacement")
.doesNotExist("replacement is not rendered");
assert.dom(".actions .reply").exists("reply button is present");
});
});

View File

@ -6,7 +6,6 @@ import DButton from "discourse/components/d-button";
import { withSilencedDeprecations } from "discourse/lib/deprecated"; import { withSilencedDeprecations } from "discourse/lib/deprecated";
import { withPluginApi } from "discourse/lib/plugin-api"; import { withPluginApi } from "discourse/lib/plugin-api";
import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { resetPostMenuExtraButtons } from "discourse/widgets/post-menu";
function postStreamTest(name, attrs) { function postStreamTest(name, attrs) {
test(name, async function (assert) { test(name, async function (assert) {
@ -21,218 +20,211 @@ function postStreamTest(name, attrs) {
} }
["enabled", "disabled"].forEach((postStreamMode) => { ["enabled", "disabled"].forEach((postStreamMode) => {
["enabled", "disabled"].forEach((postMenuMode) => { let lastTransformedPost = null;
let lastTransformedPost = null;
module( module(
`Integration | Component | Widget | post-stream (glimmer_post_stream_mode = ${postStreamMode}) (glimmer_post_menu_mode = ${postMenuMode})`, `Integration | Component | Widget | post-stream (glimmer_post_stream_mode = ${postStreamMode})`,
function (hooks) { function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.siteSettings.glimmer_post_menu_mode = postMenuMode; this.siteSettings.glimmer_post_stream_mode = postStreamMode;
this.siteSettings.glimmer_post_stream_mode = postStreamMode; });
});
hooks.afterEach(function () { const CustomPostMenuButton = <template>
resetPostMenuExtraButtons(); <DButton
}); class="hot-coffee"
...attributes
@icon="mug-saucer"
@title="coffee.title"
/>
</template>;
const CustomPostMenuButton = <template> postStreamTest("extensibility", {
<DButton posts() {
class="hot-coffee" withPluginApi("1.34.0", (api) => {
...attributes api.registerValueTransformer(
@icon="mug-saucer" "post-menu-buttons",
@title="coffee.title" ({ value: dag, context: { post, firstButtonKey } }) => {
/> dag.add("coffee", CustomPostMenuButton, {
</template>; before: firstButtonKey,
});
postStreamTest("extensibility", { // value transformers shouldn't have side effects
posts() { // we are only doing it below for testing purposes. Do not use strategies like this in the app code
withPluginApi("1.34.0", (api) => { lastTransformedPost = post;
api.registerValueTransformer( }
"post-menu-buttons",
({ value: dag, context: { post, firstButtonKey } }) => {
dag.add("coffee", CustomPostMenuButton, {
before: firstButtonKey,
});
// value transformers shouldn't have side effects
// we are only doing it below for testing purposes. Do not use strategies like this in the app code
lastTransformedPost = post;
}
);
withSilencedDeprecations(
"discourse.post-menu-widget-overrides",
() => {
api.addPostMenuButton("coffee", (transformedPost) => {
lastTransformedPost = transformedPost;
return {
action: "drinkCoffee",
icon: "mug-saucer",
className: "hot-coffee",
title: "coffee.title",
position: "first",
};
});
}
);
});
const store = getOwner(this).lookup("service:store");
const topic = store.createRecord("topic");
topic.set("details.created_by", { id: 123 });
topic.set("id", 1234);
return [
store.createRecord("post", {
topic,
id: 1,
post_number: 1,
user_id: 123,
primary_group_name: "trout",
avatar_template: "/images/avatar.png",
}),
];
},
test(assert) {
assert.dom(".post-stream").exists({ count: 1 });
assert.dom(".topic-post").exists({ count: 1 }, "renders all posts");
assert
.dom(".topic-post:nth-of-type(1) button.hot-coffee")
.exists("it transforms posts");
assert.strictEqual(
lastTransformedPost.topic.id,
1234,
"it also transforms the topic"
); );
assert
.dom(".actions .hot-coffee")
.exists({ count: 1 }, "has the extended button");
},
});
postStreamTest("basics", { withSilencedDeprecations(
posts() { "discourse.post-menu-widget-overrides",
const site = getOwner(this).lookup("service:site"); () => {
const store = getOwner(this).lookup("service:store"); api.addPostMenuButton("coffee", (transformedPost) => {
const topic = store.createRecord("topic"); lastTransformedPost = transformedPost;
topic.set("details.created_by", { id: 123 });
return [ return {
store.createRecord("post", { action: "drinkCoffee",
topic, icon: "mug-saucer",
id: 1, className: "hot-coffee",
post_number: 1, title: "coffee.title",
username: "eviltrout", position: "first",
user_id: 123, };
primary_group_name: "trout", });
avatar_template: "/images/avatar.png", }
}), );
store.createRecord("post", { });
topic,
id: 2,
post_number: 2,
post_type: site.get("post_types.moderator_action"),
}),
store.createRecord("post", {
topic,
id: 3,
post_number: 3,
hidden: true,
}),
store.createRecord("post", {
topic,
id: 4,
post_number: 4,
post_type: site.get("post_types.whisper"),
}),
store.createRecord("post", {
topic,
id: 5,
post_number: 5,
wiki: true,
via_email: true,
}),
store.createRecord("post", {
topic,
id: 6,
post_number: 6,
via_email: true,
is_auto_generated: true,
}),
];
},
async test(assert) { const store = getOwner(this).lookup("service:store");
assert.dom(".post-stream").exists({ count: 1 }); const topic = store.createRecord("topic");
assert.dom(".topic-post").exists({ count: 6 }, "renders all posts"); topic.set("details.created_by", { id: 123 });
topic.set("id", 1234);
// look for special class bindings return [
assert store.createRecord("post", {
.dom(".topic-post:nth-of-type(1).topic-owner") topic,
.exists({ count: 1 }, "applies the topic owner class"); id: 1,
assert post_number: 1,
.dom(".topic-post:nth-of-type(1).group-trout") user_id: 123,
.exists({ count: 1 }, "applies the primary group class"); primary_group_name: "trout",
assert avatar_template: "/images/avatar.png",
.dom(".topic-post:nth-of-type(1).regular") }),
.exists({ count: 1 }, "applies the regular class"); ];
assert },
.dom(".topic-post:nth-of-type(2).moderator")
.exists({ count: 1 }, "applies the moderator class");
assert
.dom(".topic-post:nth-of-type(3).post-hidden")
.exists({ count: 1 }, "applies the hidden class");
assert
.dom(".topic-post:nth-of-type(4).whisper")
.exists({ count: 1 }, "applies the whisper class");
assert
.dom(".topic-post:nth-of-type(5).wiki")
.exists({ count: 1 }, "applies the wiki class");
// it renders an article for the body with appropriate attributes test(assert) {
assert.dom("article#post_2").exists({ count: 1 }); assert.dom(".post-stream").exists({ count: 1 });
assert.dom('article[data-user-id="123"]').exists({ count: 1 }); assert.dom(".topic-post").exists({ count: 1 }, "renders all posts");
assert.dom('article[data-post-id="3"]').exists({ count: 1 }); assert
assert.dom("article#post_5.via-email").exists({ count: 1 }); .dom(".topic-post:nth-of-type(1) button.hot-coffee")
assert.dom("article#post_6.is-auto-generated").exists({ count: 1 }); .exists("it transforms posts");
assert.strictEqual(
lastTransformedPost.topic.id,
1234,
"it also transforms the topic"
);
assert
.dom(".actions .hot-coffee")
.exists({ count: 1 }, "has the extended button");
},
});
assert postStreamTest("basics", {
.dom("article:nth-of-type(1) .main-avatar") posts() {
.exists({ count: 1 }, "renders the main avatar"); const site = getOwner(this).lookup("service:site");
}, const store = getOwner(this).lookup("service:store");
}); const topic = store.createRecord("topic");
topic.set("details.created_by", { id: 123 });
postStreamTest("deleted posts", { return [
posts() { store.createRecord("post", {
const store = getOwner(this).lookup("service:store"); topic,
const topic = store.createRecord("topic"); id: 1,
topic.set("details.created_by", { id: 123 }); post_number: 1,
username: "eviltrout",
user_id: 123,
primary_group_name: "trout",
avatar_template: "/images/avatar.png",
}),
store.createRecord("post", {
topic,
id: 2,
post_number: 2,
post_type: site.get("post_types.moderator_action"),
}),
store.createRecord("post", {
topic,
id: 3,
post_number: 3,
hidden: true,
}),
store.createRecord("post", {
topic,
id: 4,
post_number: 4,
post_type: site.get("post_types.whisper"),
}),
store.createRecord("post", {
topic,
id: 5,
post_number: 5,
wiki: true,
via_email: true,
}),
store.createRecord("post", {
topic,
id: 6,
post_number: 6,
via_email: true,
is_auto_generated: true,
}),
];
},
return [ async test(assert) {
store.createRecord("post", { assert.dom(".post-stream").exists({ count: 1 });
topic, assert.dom(".topic-post").exists({ count: 6 }, "renders all posts");
id: 1,
post_number: 1,
deleted_at: new Date().toString(),
}),
];
},
test(assert) { // look for special class bindings
assert assert
.dom(".topic-post.deleted") .dom(".topic-post:nth-of-type(1).topic-owner")
.exists({ count: 1 }, "applies the deleted class"); .exists({ count: 1 }, "applies the topic owner class");
assert assert
.dom(".deleted-user-avatar") .dom(".topic-post:nth-of-type(1).group-trout")
.exists({ count: 1 }, "has the trash avatar"); .exists({ count: 1 }, "applies the primary group class");
}, assert
}); .dom(".topic-post:nth-of-type(1).regular")
} .exists({ count: 1 }, "applies the regular class");
); assert
}); .dom(".topic-post:nth-of-type(2).moderator")
.exists({ count: 1 }, "applies the moderator class");
assert
.dom(".topic-post:nth-of-type(3).post-hidden")
.exists({ count: 1 }, "applies the hidden class");
assert
.dom(".topic-post:nth-of-type(4).whisper")
.exists({ count: 1 }, "applies the whisper class");
assert
.dom(".topic-post:nth-of-type(5).wiki")
.exists({ count: 1 }, "applies the wiki class");
// it renders an article for the body with appropriate attributes
assert.dom("article#post_2").exists({ count: 1 });
assert.dom('article[data-user-id="123"]').exists({ count: 1 });
assert.dom('article[data-post-id="3"]').exists({ count: 1 });
assert.dom("article#post_5.via-email").exists({ count: 1 });
assert.dom("article#post_6.is-auto-generated").exists({ count: 1 });
assert
.dom("article:nth-of-type(1) .main-avatar")
.exists({ count: 1 }, "renders the main avatar");
},
});
postStreamTest("deleted posts", {
posts() {
const store = getOwner(this).lookup("service:store");
const topic = store.createRecord("topic");
topic.set("details.created_by", { id: 123 });
return [
store.createRecord("post", {
topic,
id: 1,
post_number: 1,
deleted_at: new Date().toString(),
}),
];
},
test(assert) {
assert
.dom(".topic-post.deleted")
.exists({ count: 1 }, "applies the deleted class");
assert
.dom(".deleted-user-avatar")
.exists({ count: 1 }, "has the trash avatar");
},
});
}
);
}); });

View File

@ -9,7 +9,6 @@ module("Unit | Component | post-menu", function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.siteSettings.glimmer_post_menu_mode = "enabled";
this.siteSettings.post_menu = this.siteSettings.post_menu =
"read|like|copyLink|share|flag|edit|bookmark|delete|admin|reply"; "read|like|copyLink|share|flag|edit|bookmark|delete|admin|reply";
this.siteSettings.post_menu_hidden_items = ""; this.siteSettings.post_menu_hidden_items = "";

View File

@ -370,7 +370,6 @@ nav.post-controls {
&.like { &.like {
// Like button with 0 likes // Like button with 0 likes
&.d-hover,
&:hover { &:hover {
background: var(--love-low); background: var(--love-low);
@ -387,7 +386,6 @@ nav.post-controls {
} }
.discourse-no-touch & { .discourse-no-touch & {
&.d-hover,
&:hover { &:hover {
background: var(--primary-low); background: var(--primary-low);
@ -410,7 +408,6 @@ nav.post-controls {
// Like button when like count is present // Like button when like count is present
.discourse-no-touch & { .discourse-no-touch & {
&.d-hover,
&:hover { &:hover {
background: var(--primary-low); background: var(--primary-low);
} }
@ -419,7 +416,6 @@ nav.post-controls {
// Like count button // Like count button
.discourse-no-touch & { .discourse-no-touch & {
&.d-hover,
&:hover { &:hover {
color: var(--primary); color: var(--primary);
} }
@ -459,14 +455,7 @@ nav.post-controls {
border: none; border: none;
border-radius: var(--d-post-control-border-radius); border-radius: var(--d-post-control-border-radius);
.d-icon {
// this avoids an issue where hovering off the icon
// removes the .d-hover class from the button prematurely
pointer-events: none;
}
.discourse-no-touch & { .discourse-no-touch & {
&.d-hover,
&:hover, &:hover,
&:focus-visible, &:focus-visible,
&:active { &:active {
@ -475,8 +464,6 @@ nav.post-controls {
color: var(--d-post-control-text-color--hover); color: var(--d-post-control-text-color--hover);
} }
// TODO (glimmer-post-menu): Go over the the d-hover style and remove the unnecessary ones when glimmer-post-menu replaces the widget version
&.delete.d-hover,
&.delete:hover, &.delete:hover,
&.delete:active, &.delete:active,
&.delete:focus { &.delete:focus {
@ -612,8 +599,6 @@ nav.post-controls {
} }
} }
// .d-icon.heart-animation is the widget animation while .toggle-like.heart-animation is the glimmer-post-menu's
.has-like .d-icon.heart-animation,
.toggle-like.heart-animation .d-icon { .toggle-like.heart-animation .d-icon {
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
animation: heartBump 0.4s; animation: heartBump 0.4s;

View File

@ -2758,8 +2758,6 @@ en:
default_navigation_menu_tags: "Selected tags will be displayed under Navigation Menu's Tags section by default." default_navigation_menu_tags: "Selected tags will be displayed under Navigation Menu's Tags section by default."
experimental_new_new_view_groups: 'Enable a new topics list that combines unread and new topics and make the "Everything" link in the sidebar link to it.' experimental_new_new_view_groups: 'Enable a new topics list that combines unread and new topics and make the "Everything" link in the sidebar link to it.'
glimmer_topic_list_mode: "Control whether the new 'glimmer' topic-list implementation is used. 'auto' will enable automatically once all your themes and plugins are ready. See <a href='https://meta.discourse.org/t/343404'>the Meta topic</a> for more information." glimmer_topic_list_mode: "Control whether the new 'glimmer' topic-list implementation is used. 'auto' will enable automatically once all your themes and plugins are ready. See <a href='https://meta.discourse.org/t/343404'>the Meta topic</a> for more information."
glimmer_post_menu_mode: "Control whether the new 'glimmer' post menu implementation is used. 'auto' will enable automatically once all your themes and plugins are ready. This implementation is under active development, and is not intended for production use. Do not develop themes/plugins against it until the implementation is finalized and announced."
glimmer_post_menu_groups: "Enable the new 'glimmer' post menu implementation in 'auto' mode for the specified user groups. This implementation is under active development, and is not intended for production use. Do not develop themes/plugins against it until the implementation is finalized and announced."
glimmer_post_stream_mode: "Control whether the new 'glimmer' post stream implementation is used. 'auto' will enable automatically once all your themes and plugins are ready. This implementation is under active development, and is not intended for production use. Do not develop themes/plugins against it until the implementation is finalized and announced." glimmer_post_stream_mode: "Control whether the new 'glimmer' post stream implementation is used. 'auto' will enable automatically once all your themes and plugins are ready. This implementation is under active development, and is not intended for production use. Do not develop themes/plugins against it until the implementation is finalized and announced."
glimmer_post_stream_mode_auto_groups: "Enable the new 'glimmer' post menu implementation in 'auto' mode for the specified user groups. This implementation is under active development, and is not intended for production use. Do not develop themes/plugins against it until the implementation is finalized and announced." glimmer_post_stream_mode_auto_groups: "Enable the new 'glimmer' post menu implementation in 'auto' mode for the specified user groups. This implementation is under active development, and is not intended for production use. Do not develop themes/plugins against it until the implementation is finalized and announced."
experimental_form_templates: "Enable the form templates feature. Manage the templates at <a href='%{base_path}/admin/customize/form-templates'>Customize / Templates</a>." experimental_form_templates: "Enable the form templates feature. Manage the templates at <a href='%{base_path}/admin/customize/form-templates'>Customize / Templates</a>."

View File

@ -3552,14 +3552,6 @@ experimental:
- auto - auto
- enabled - enabled
default: enabled default: enabled
glimmer_post_menu_mode:
client: true
type: enum
choices:
- disabled
- auto
- enabled
default: enabled
glimmer_post_stream_mode_auto_groups: glimmer_post_stream_mode_auto_groups:
client: true client: true
type: group_list type: group_list

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class RemoveGlimmerPostMenuModeSetting < ActiveRecord::Migration[7.2]
def up
execute <<~SQL
DELETE FROM site_settings
WHERE name = 'glimmer_post_menu_mode'
SQL
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View File

@ -330,36 +330,35 @@ module PageObjects
end end
def selector_for_post_action_button(button) def selector_for_post_action_button(button)
# TODO (glimmer-post-menu): Replace the selector with the BEM format ones once the glimmer-post-menu replaces the widget post menu
case button case button
when :admin when :admin
".post-controls .show-post-admin-menu" ".post-controls .post-action-menu__admin"
when :bookmark when :bookmark
".post-controls .bookmark" ".post-controls .post-action-menu__bookmark"
when :copy_link, :copyLink when :copy_link, :copyLink
".post-controls .post-action-menu__copy-link" ".post-controls .post-action-menu__copy-link"
when :delete when :delete
".post-controls .delete" ".post-controls .post-action-menu__delete"
when :edit when :edit
".post-controls .edit" ".post-controls .post-action-menu__edit"
when :flag when :flag
".post-controls .create-flag" ".post-controls .post-action-menu__flag"
when :like when :like
".post-controls .toggle-like" ".post-controls .post-action-menu__like"
when :like_count when :like_count
".post-controls .like-count" ".post-controls .post-action-menu__like-count"
when :read when :read
".post-controls .read-indicator" ".post-controls .post-action-menu__read"
when :recover when :recover
".post-controls .recover" ".post-controls .post-action-menu__recover"
when :replies when :replies
".post-controls .show-replies" ".post-controls .post-action-menu__show-replies"
when :reply when :reply
".post-controls .reply" ".post-controls .post-action-menu__reply"
when :share when :share
".post-controls .share" ".post-controls .post-action-menu__share"
when :show_more when :show_more
".post-controls .show-more-actions" ".post-controls .post-action-menu__show-more"
else else
raise "Unknown post menu button type: #{button}" raise "Unknown post menu button type: #{button}"
end end

File diff suppressed because it is too large Load Diff