{{ action.label }}
{
const action = visibleActions.value[actionIndex] as DropdownAction;
- if (action && action.type === "dropdown" && action.options[optionIndex]) {
- selectDropdownOption(action, action.options[optionIndex]);
+ if (action && action.type === "dropdown") {
+ const visibleOptions = getVisibleDropdownOptions(action);
+ if (optionIndex < visibleOptions.length) {
+ selectDropdownOption(action, visibleOptions[optionIndex]);
+ }
}
},
tryToggleChip: (actionIndex: number, chipIndex: number) => {
const action = visibleActions.value[actionIndex] as MultiSelectChipsAction;
if (action && action.type === "multi-select-chips") {
- toggleChip(action, chipIndex);
+ const visibleOptions = getVisibleMultiSelectOptions(action);
+ if (chipIndex < visibleOptions.length) {
+ toggleChip(action, chipIndex);
+ }
}
},
@@ -733,13 +741,17 @@ const multiSelectActions = computed(() =>
function getDropdownValue(action: DropdownAction) {
const actionId = getActionId(action);
+ const visibleOptions = getVisibleDropdownOptions(action);
const currentValue = actionStates.value[actionId]?.value ?? action.defaultOption ?? 0;
- if (action.options && action.options[currentValue]) {
- return action.options[currentValue];
+ const allOptions = action.options;
+ const storedOption = allOptions[currentValue];
+
+ if (storedOption && visibleOptions.includes(storedOption)) {
+ return storedOption;
}
- return action.options?.[0] || null;
+ return visibleOptions[0] || null;
}
function isActionSelected(action: Action): boolean {
@@ -775,20 +787,31 @@ function selectDropdownOption(action: DropdownAction, selected: any) {
function isChipSelected(action: MultiSelectChipsAction, optionIndex: number): boolean {
const actionId = getActionId(action);
const selectedSet = actionStates.value[actionId]?.value as Set | undefined;
- return selectedSet?.has(optionIndex) || false;
+
+ const visibleOptions = getVisibleMultiSelectOptions(action);
+ const visibleOption = visibleOptions[optionIndex];
+ const originalIndex = action.options.findIndex((opt) => opt === visibleOption);
+
+ return selectedSet?.has(originalIndex) || false;
}
function toggleChip(action: MultiSelectChipsAction, optionIndex: number) {
const actionId = getActionId(action);
const state = actionStates.value[actionId];
if (state && state.value instanceof Set) {
- if (state.value.has(optionIndex)) {
- state.value.delete(optionIndex);
- } else {
- state.value.add(optionIndex);
+ const visibleOptions = getVisibleMultiSelectOptions(action);
+ const visibleOption = visibleOptions[optionIndex];
+ const originalIndex = action.options.findIndex((opt) => opt === visibleOption);
+
+ if (originalIndex !== -1) {
+ if (state.value.has(originalIndex)) {
+ state.value.delete(originalIndex);
+ } else {
+ state.value.add(originalIndex);
+ }
+ state.selected = state.value.size > 0;
+ persistState();
}
- state.selected = state.value.size > 0;
- persistState();
}
}
@@ -869,9 +892,23 @@ async function processAction(
stageIndex: number,
messageParts: MessagePart[],
) {
+ const allValidActionIds: string[] = [];
+ checklist.forEach((stage, stageIdx) => {
+ stage.actions.forEach((stageAction, actionIdx) => {
+ allValidActionIds.push(getActionIdForStage(stageAction, stageIdx, actionIdx));
+ if (stageAction.enablesActions) {
+ stageAction.enablesActions.forEach((enabledAction, enabledIdx) => {
+ allValidActionIds.push(
+ getActionIdForStage(enabledAction, stageIdx, actionIdx, enabledIdx),
+ );
+ });
+ }
+ });
+ });
+
if (action.type === "button" || action.type === "toggle") {
const buttonAction = action as ButtonAction | ToggleAction;
- const message = await getActionMessage(buttonAction, selectedActionIds);
+ const message = await getActionMessage(buttonAction, selectedActionIds, allValidActionIds);
if (message) {
messageParts.push({
weight: buttonAction.weight,
@@ -885,6 +922,7 @@ async function processAction(
const matchingVariant = findMatchingVariant(
conditionalAction.messageVariants,
selectedActionIds,
+ allValidActionIds,
);
if (matchingVariant) {
const message = (await matchingVariant.message()) as string;
@@ -944,6 +982,24 @@ function shouldShowAction(action: Action): boolean {
return true;
}
+function getVisibleDropdownOptions(action: DropdownAction) {
+ return action.options.filter((option) => {
+ if (typeof option.shouldShow === "function") {
+ return option.shouldShow(props.project);
+ }
+ return true;
+ });
+}
+
+function getVisibleMultiSelectOptions(action: MultiSelectChipsAction) {
+ return action.options.filter((option) => {
+ if (typeof option.shouldShow === "function") {
+ return option.shouldShow(props.project);
+ }
+ return true;
+ });
+}
+
function shouldShowStageIndex(stageIndex: number): boolean {
return shouldShowStage(checklist[stageIndex]);
}
diff --git a/packages/moderation/types/actions.ts b/packages/moderation/types/actions.ts
index b92148fba..46b3dd8ce 100644
--- a/packages/moderation/types/actions.ts
+++ b/packages/moderation/types/actions.ts
@@ -153,6 +153,13 @@ export interface DropdownActionOption extends WeightedMessage {
* The label of the option, which is displayed to the moderator.
*/
label: string
+
+ /**
+ * A function that determines whether this option should be shown for a given project.
+ *
+ * By default, it returns `true`, meaning the option is always shown.
+ */
+ shouldShow?: (project: Project) => boolean
}
export interface DropdownAction extends BaseAction {
@@ -179,6 +186,13 @@ export interface MultiSelectChipsOption extends WeightedMessage {
* The label of the chip, which is displayed to the moderator.
*/
label: string
+
+ /**
+ * A function that determines whether this option should be shown for a given project.
+ *
+ * By default, it returns `true`, meaning the option is always shown.
+ */
+ shouldShow?: (project: Project) => boolean
}
export interface MultiSelectChipsAction extends BaseAction {
diff --git a/packages/moderation/utils.ts b/packages/moderation/utils.ts
index 50f637116..2d43bb88f 100644
--- a/packages/moderation/utils.ts
+++ b/packages/moderation/utils.ts
@@ -125,13 +125,19 @@ export function processMessage(
export function findMatchingVariant(
variants: ConditionalMessage[],
selectedActionIds: string[],
+ allValidActionIds?: string[],
): ConditionalMessage | null {
for (const variant of variants) {
const conditions = variant.conditions
const meetsRequired =
!conditions.requiredActions ||
- conditions.requiredActions.every((id) => selectedActionIds.includes(id))
+ conditions.requiredActions.every((id) => {
+ if (allValidActionIds && !allValidActionIds.includes(id)) {
+ return false
+ }
+ return selectedActionIds.includes(id)
+ })
const meetsExcluded =
!conditions.excludedActions ||
@@ -148,9 +154,14 @@ export function findMatchingVariant(
export async function getActionMessage(
action: ButtonAction | ToggleAction,
selectedActionIds: string[],
+ allValidActionIds?: string[],
): Promise {
if (action.conditionalMessages && action.conditionalMessages.length > 0) {
- const matchingConditional = findMatchingVariant(action.conditionalMessages, selectedActionIds)
+ const matchingConditional = findMatchingVariant(
+ action.conditionalMessages,
+ selectedActionIds,
+ allValidActionIds,
+ )
if (matchingConditional) {
return (await matchingConditional.message()) as string
}
diff --git a/packages/ui/src/components/base/DropdownSelect.vue b/packages/ui/src/components/base/DropdownSelect.vue
index 0642c4b57..c87ef340b 100644
--- a/packages/ui/src/components/base/DropdownSelect.vue
+++ b/packages/ui/src/components/base/DropdownSelect.vue
@@ -103,6 +103,10 @@ const props = defineProps({
type: Function,
default: undefined,
},
+ maxVisibleOptions: {
+ type: Number,
+ default: undefined,
+ },
})
function getOptionLabel(option) {
@@ -263,7 +267,7 @@ const isChildOfDropdown = (element) => {
.options {
z-index: 10;
- max-height: 18.75rem;
+ max-height: v-bind('maxVisibleOptions ? `calc(${maxVisibleOptions} * 3rem)` : "18.75rem"');
overflow-y: auto;
box-shadow:
var(--shadow-inset-sm),