Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* @param secondaryButtonCommand command template the secondary button should execute in post-join dialogs
* (null for pre-join dialogs where the secondary button is a cancel action)
* @param body optional translated body text shown below the title (null = no body)
* @param stackButtonsVertically whether buttons should be stacked vertically instead of shown side by side
*/
public record DialogWindowSpec(String title,
List<DialogInputSpec> inputs,
Expand All @@ -23,7 +24,8 @@ public record DialogWindowSpec(String title,
boolean showSecondaryButton,
boolean canCloseWithEscape,
String secondaryButtonCommand,
String body) {
String body,
boolean stackButtonsVertically) {

public DialogWindowSpec {
Objects.requireNonNull(title, "title");
Expand All @@ -42,12 +44,17 @@ public DialogWindowSpec(String title,
boolean canCloseWithEscape,
String secondaryButtonCommand) {
this(title, inputs, primaryButtonLabel, secondaryButtonLabel,
showSecondaryButton, canCloseWithEscape, secondaryButtonCommand, null);
showSecondaryButton, canCloseWithEscape, secondaryButtonCommand, null, false);
}

/** Returns a copy of this spec with the given body text (may be null). */
public DialogWindowSpec withBody(String newBody) {
return new DialogWindowSpec(title, inputs, primaryButtonLabel, secondaryButtonLabel,
showSecondaryButton, canCloseWithEscape, secondaryButtonCommand, newBody);
showSecondaryButton, canCloseWithEscape, secondaryButtonCommand, newBody, stackButtonsVertically);
}

/** Returns the number of columns the dialog buttons should be arranged in. */
public int buttonColumns() {
return stackButtonsVertically ? 1 : 2;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public DialogWindowSpec createPreJoinLoginDialog(String playerName) {
showRecovery || showCancelButton,
canCloseWithEscape,
showRecovery ? "forgot_password" : null,
showBody ? text.apply(MessageKey.DIALOG_LOGIN_BODY) : null);
showBody ? text.apply(MessageKey.DIALOG_LOGIN_BODY) : null,
commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY));
}

public DialogWindowSpec createPreJoinRecoveryDialog(String playerName) {
Expand All @@ -82,7 +83,8 @@ public DialogWindowSpec createPreJoinRecoveryDialog(String playerName) {
true,
false,
null,
showBody ? text.apply(MessageKey.DIALOG_RECOVERY_BODY) : null);
showBody ? text.apply(MessageKey.DIALOG_RECOVERY_BODY) : null,
commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY));
}

public DialogWindowSpec createTotpDialog(Player player) {
Expand All @@ -99,7 +101,8 @@ public DialogWindowSpec createTotpDialog(Player player) {
false,
false,
null,
showBody ? text.apply(MessageKey.DIALOG_TWO_FACTOR_BODY) : null);
showBody ? text.apply(MessageKey.DIALOG_TWO_FACTOR_BODY) : null,
commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY));
}

public DialogWindowSpec createRegisterDialog(Player player,
Expand Down Expand Up @@ -144,7 +147,8 @@ private DialogWindowSpec createLoginDialogSpec(Function<MessageKey, String> text
showSecondaryButton,
canCloseWithEscape,
showRecovery ? "email recover $(email)" : null,
showBody ? textResolver.apply(MessageKey.DIALOG_LOGIN_BODY) : null);
showBody ? textResolver.apply(MessageKey.DIALOG_LOGIN_BODY) : null,
commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY));
}

private DialogWindowSpec createRegisterDialogSpec(RegistrationType type,
Expand Down Expand Up @@ -194,7 +198,8 @@ private DialogWindowSpec createRegisterDialogSpec(RegistrationType type,
showSecondaryButton,
canCloseWithEscape,
null,
showBody ? textResolver.apply(MessageKey.DIALOG_REGISTER_BODY) : null);
showBody ? textResolver.apply(MessageKey.DIALOG_REGISTER_BODY) : null,
commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY));
}

private String getPostJoinMessage(Player player, MessageKey key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ public final class RegistrationSettings implements SettingsHolder {
public static final Property<Boolean> DIALOG_SHOW_BODY =
newProperty("settings.registration.dialog.showBody", true);

@Comment({
"Stack dialog buttons vertically, with the primary button on top.",
"If disabled, dialog buttons are shown side by side."})
public static final Property<Boolean> DIALOG_STACK_BUTTONS_VERTICALLY =
newProperty("settings.registration.dialog.stackButtonsVertically", false);

@Comment({
"Show the login/register dialog during the Paper/Folia pre-join phase.",
"Independent from postJoin.enable; you may enable either or both options.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ void shouldBuildPostJoinLoginDialogWithForgotPasswordButtonWhenEmailServiceConfi
.willAnswer(invocation -> invocation.getArgument(1, MessageKey.class).getKey());
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_FORGOT_PASSWORD_BUTTON)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_BODY)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY)).willReturn(false);
given(emailService.hasAllInformation()).willReturn(true);

// when
Expand All @@ -73,6 +74,7 @@ void shouldNotShowForgotPasswordButtonWhenEmailServiceNotConfigured() {
.willAnswer(invocation -> invocation.getArgument(1, MessageKey.class).getKey());
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_FORGOT_PASSWORD_BUTTON)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_BODY)).willReturn(false);
given(commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY)).willReturn(false);
given(emailService.hasAllInformation()).willReturn(false);

// when — email service not configured: recovery must not appear regardless of the flag
Expand All @@ -92,6 +94,7 @@ void shouldBuildPostJoinLoginDialogWithoutForgotPasswordButtonWhenDisabledByConf
.willAnswer(invocation -> invocation.getArgument(1, MessageKey.class).getKey());
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_FORGOT_PASSWORD_BUTTON)).willReturn(false);
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_BODY)).willReturn(false);
given(commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY)).willReturn(false);

// when — disabled by config even when email service is available
DialogWindowSpec dialog = dialogWindowService.createLoginDialog(player);
Expand All @@ -110,6 +113,7 @@ void shouldBuildPostJoinRegisterDialogWithEmailBeforePasswordForMandatoryEmailFl
given(commonService.retrieveSingleMessage(eq(player), any(MessageKey.class)))
.willAnswer(invocation -> invocation.getArgument(1, MessageKey.class).getKey());
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_BODY)).willReturn(false);
given(commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY)).willReturn(false);

// when
DialogWindowSpec dialog = dialogWindowService.createRegisterDialog(
Expand All @@ -126,6 +130,7 @@ void shouldBuildPostJoinRegisterDialogForOptionalEmailFlow() {
given(commonService.retrieveSingleMessage(eq(player), any(MessageKey.class)))
.willAnswer(invocation -> invocation.getArgument(1, MessageKey.class).getKey());
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_BODY)).willReturn(false);
given(commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY)).willReturn(false);

// when
DialogWindowSpec dialog = dialogWindowService.createRegisterDialog(
Expand All @@ -148,6 +153,7 @@ void shouldBuildPreJoinLoginDialogWithCancelButtonWhenEmailServiceNotConfigured(
given(commonService.getProperty(RegistrationSettings.PRE_JOIN_DIALOG_SHOW_CANCEL_BUTTON)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.PRE_JOIN_DIALOG_ALLOW_CLOSE_WITH_ESCAPE)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_BODY)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY)).willReturn(false);
given(emailService.hasAllInformation()).willReturn(false);

// when — email not configured: no recovery, cancel button shown instead
Expand All @@ -160,6 +166,7 @@ void shouldBuildPreJoinLoginDialogWithCancelButtonWhenEmailServiceNotConfigured(
assertThat(dialog.showSecondaryButton(), is(true));
assertThat(dialog.canCloseWithEscape(), is(true));
assertThat(dialog.body(), is("dialog.login.body"));
assertThat(dialog.buttonColumns(), is(2));
assertThat(dialog.inputs().stream().map(input -> input.id()).toList(), contains("password"));
assertThat(dialog.secondaryButtonCommand(), is(nullValue()));
}
Expand All @@ -171,6 +178,7 @@ void shouldBuildPreJoinLoginDialogWithRecoveryButtonWhenEmailServiceConfigured()
.willAnswer(invocation -> invocation.getArgument(1, MessageKey.class).getKey());
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_FORGOT_PASSWORD_BUTTON)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_BODY)).willReturn(false);
given(commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY)).willReturn(false);
given(emailService.hasAllInformation()).willReturn(true);

// when — email configured: recovery button shown, cancel suppressed, email field present
Expand All @@ -183,4 +191,24 @@ void shouldBuildPreJoinLoginDialogWithRecoveryButtonWhenEmailServiceConfigured()
assertThat(dialog.secondaryButtonCommand(), is(notNullValue()));
assertThat(dialog.inputs().stream().map(input -> input.id()).toList(), contains("password"));
}

@Test
void shouldKeepStackedButtonsInPreJoinRegisterDialogWhenBodyIsReplaced() {
// given
given(messages.retrieveSingle(eq("Bobby"), any(MessageKey.class)))
.willAnswer(invocation -> invocation.getArgument(1, MessageKey.class).getKey());
given(commonService.getProperty(RegistrationSettings.PRE_JOIN_DIALOG_SHOW_CANCEL_BUTTON)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.PRE_JOIN_DIALOG_ALLOW_CLOSE_WITH_ESCAPE)).willReturn(true);
given(commonService.getProperty(RegistrationSettings.DIALOG_SHOW_BODY)).willReturn(false);
given(commonService.getProperty(RegistrationSettings.DIALOG_STACK_BUTTONS_VERTICALLY)).willReturn(true);

// when
DialogWindowSpec dialog = dialogWindowService.createPreJoinRegisterDialog(
"Bobby", RegistrationType.PASSWORD, RegisterSecondaryArgument.CONFIRMATION).withBody("error");

// then
assertThat(dialog.body(), is("error"));
assertThat(dialog.showSecondaryButton(), is(true));
assertThat(dialog.buttonColumns(), is(1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static Dialog createPreJoinLoginDialog(DialogWindowSpec dialog) {

return Dialog.create(factory -> factory.empty()
.base(base)
.type(DialogType.multiAction(createPreJoinButtons(dialog, loginButton, secondaryKey)).build()));
.type(createButtonDialogType(dialog, createPreJoinButtons(dialog, loginButton, secondaryKey))));
}

public static Dialog createPreJoinRecoveryDialog(DialogWindowSpec dialog) {
Expand All @@ -80,8 +80,8 @@ public static Dialog createPreJoinRecoveryDialog(DialogWindowSpec dialog) {

return Dialog.create(factory -> factory.empty()
.base(base)
.type(DialogType.multiAction(createPreJoinButtons(dialog, submitButton,
PaperDialogActionKeys.PRE_JOIN_RECOVERY_CANCEL)).build()));
.type(createButtonDialogType(dialog, createPreJoinButtons(dialog, submitButton,
PaperDialogActionKeys.PRE_JOIN_RECOVERY_CANCEL))));
}

public static Dialog createPreJoinRegisterDialog(DialogWindowSpec dialog) {
Expand All @@ -98,9 +98,9 @@ public static Dialog createPreJoinRegisterDialog(DialogWindowSpec dialog) {

return Dialog.create(factory -> factory.empty()
.base(base)
.type(DialogType.multiAction(createPreJoinButtons(dialog,
.type(createButtonDialogType(dialog, createPreJoinButtons(dialog,
registerButton,
PaperDialogActionKeys.PRE_JOIN_REGISTER_CANCEL)).build()));
PaperDialogActionKeys.PRE_JOIN_REGISTER_CANCEL))));
}

private static Dialog createInGameCommandDialog(DialogWindowSpec dialog, String commandTemplate) {
Expand All @@ -124,7 +124,11 @@ private static Dialog createInGameCommandDialog(DialogWindowSpec dialog, String

return Dialog.create(factory -> factory.empty()
.base(base)
.type(DialogType.multiAction(buttons).build()));
.type(createButtonDialogType(dialog, buttons)));
}

private static DialogType createButtonDialogType(DialogWindowSpec dialog, List<ActionButton> buttons) {
return DialogType.multiAction(buttons).columns(dialog.buttonColumns()).build();
}

private static List<ActionButton> createPreJoinButtons(DialogWindowSpec dialog,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ static void showLoginDialog(Player player, DialogWindowSpec dialog) {
buttons.add(new ActionButton(toTextComponent(dialog.secondaryButtonLabel()),
new RunCommandAction(dialog.secondaryButtonCommand())));
}
player.showDialog(new MultiActionDialog(base, buttons.toArray(new ActionButton[0])));
player.showDialog(new MultiActionDialog(base, buttons.toArray(new ActionButton[0]))
.columns(dialog.buttonColumns()));
}

static void showTotpDialog(Player player, DialogWindowSpec dialog) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ public void showLoginDialogSendsCorrectCommandTemplate() {
assertThat(action.template(), is("login $(password)"));
}

@Test
public void showLoginDialogStacksButtonsWhenEnabled() {
// given
Player player = mock(Player.class);
DialogWindowSpec dialog = new DialogWindowSpec("Login",
java.util.List.of(new DialogInputSpec("password", "Password", 100)),
"Login",
"Forgot Password?",
true,
false,
"email recover $(email)",
null,
true);

// when
SpigotDialogHelper.showLoginDialog(player, dialog);

// then
ArgumentCaptor<MultiActionDialog> captor = ArgumentCaptor.forClass(MultiActionDialog.class);
verify(player).showDialog(captor.capture());
assertThat(captor.getValue().columns(), is(1));
}

@Test
public void showLoginDialogIncludesForgotPasswordButtonWhenSpecified() {
// given
Expand Down
7 changes: 5 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Fri Sep 18 18:25:33 CST 2026. See authme-tools/src/test/java/tools/docs/config/config.tpl.md -->
<!-- File auto-generated on Mon Nov 02 14:57:12 CLST 2026. See authme-tools/src/test/java/tools/docs/config/config.tpl.md -->

## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
Expand Down Expand Up @@ -335,6 +335,9 @@ settings:
# Show a short description body in login, register, and two-factor dialogs.
# The body text helps players understand what the dialog is asking them to do.
showBody: true
# Stack dialog buttons vertically, with the primary button on top.
# If disabled, dialog buttons are shown side by side.
stackButtonsVertically: false
preJoin:
# Show the login/register dialog during the Paper/Folia pre-join phase.
# Independent from postJoin.enable; you may enable either or both options.
Expand Down Expand Up @@ -650,4 +653,4 @@ To change settings on a running server, save your changes to config.yml and use

---

This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Sep 18 18:25:33 CST 2026
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon Nov 02 14:57:12 CLST 2026
Loading