diff --git a/assets/chat/css/command-menus/_index.scss b/assets/chat/css/command-menus/_index.scss
new file mode 100644
index 00000000..083c8d7b
--- /dev/null
+++ b/assets/chat/css/command-menus/_index.scss
@@ -0,0 +1,58 @@
+@use '~@destinygg/libstiny' as dgg;
+
+@use 'poll';
+
+.command-menu {
+ display: none;
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 132;
+ border: 1px solid dgg.$semantic-border-default;
+ border-radius: dgg.$semantic-radii-x-small dgg.$semantic-radii-x-small 0 0;
+ background-color: dgg.$semantic-background-default;
+ padding: dgg.$space-2;
+
+ &--shown {
+ display: block;
+ }
+
+ &__title {
+ display: flex;
+ justify-content: center;
+ margin-bottom: dgg.$space-2;
+ border-bottom: 1px solid dgg.$semantic-border-default;
+ padding-bottom: dgg.$space-2;
+ color: dgg.$semantic-foreground-default;
+ font: dgg.$body-500-bold;
+ }
+
+ &__content {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ }
+
+ &__section {
+ padding: dgg.$space-2;
+
+ &:not(:last-child) {
+ border-bottom: 1px solid dgg.$semantic-border-default;
+ }
+ }
+
+ &__actions {
+ display: flex;
+ justify-content: space-between;
+ border-top: 1px solid dgg.$semantic-border-default;
+ padding: dgg.$space-2 0;
+ }
+
+ &__input {
+ display: flex;
+ justify-content: center;
+ margin-bottom: dgg.$space-2;
+ width: 100%;
+ }
+}
diff --git a/assets/chat/css/command-menus/_poll.scss b/assets/chat/css/command-menus/_poll.scss
new file mode 100644
index 00000000..e9074b5c
--- /dev/null
+++ b/assets/chat/css/command-menus/_poll.scss
@@ -0,0 +1,13 @@
+@use '~@destinygg/libstiny' as dgg;
+
+#command-menu-poll {
+ .command-menu-poll-answer {
+ &:nth-child(-n + 2) .input__suffix {
+ display: none;
+ }
+
+ .input__suffix {
+ padding: dgg.$space-1;
+ }
+ }
+}
diff --git a/assets/chat/css/style.scss b/assets/chat/css/style.scss
index cae4cfee..bda1f9ea 100644
--- a/assets/chat/css/style.scss
+++ b/assets/chat/css/style.scss
@@ -1,14 +1,17 @@
@use 'sass:color';
+@use '~@destinygg/libstiny';
+
@use '~normalize.css/normalize';
@use '~tippy.js/dist/tippy';
@use '~tippy.js/dist/svg-arrow';
@use 'abstracts' as a;
@use 'chat';
+@use 'command-menus';
+@use 'components';
@use 'menus';
@use 'messages';
-@use 'components';
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500,600,700');
diff --git a/assets/chat/js/autocomplete.js b/assets/chat/js/autocomplete.js
index dbbba009..a4c4ffab 100644
--- a/assets/chat/js/autocomplete.js
+++ b/assets/chat/js/autocomplete.js
@@ -128,9 +128,10 @@ function selectHelper(ac) {
}
class ChatAutoComplete {
- constructor() {
+ constructor(chat) {
+ this.chat = chat;
/** @member jQuery */
- this.ui = $(`
`);
+ this.ui = this.chat.ui.find('#chat-auto-complete');
this.ui.on('click', 'li', (e) =>
this.select(parseInt(e.currentTarget.getAttribute('data-index'), 10)),
);
@@ -145,7 +146,6 @@ class ChatAutoComplete {
bind(chat) {
this.chat = chat;
this.input = chat.input;
- this.ui.insertBefore(chat.input);
let originval = '';
let shiftdown = false;
let keypressed = false;
diff --git a/assets/chat/js/chat.js b/assets/chat/js/chat.js
index e4ac0dbc..88dc08f1 100644
--- a/assets/chat/js/chat.js
+++ b/assets/chat/js/chat.js
@@ -33,6 +33,7 @@ import {
ChatUserInfoMenu,
ChatEventActionMenu,
} from './menus';
+import { IconsController } from './icons';
import ChatEventBar from './event-bar/EventBar';
import ChatAutoComplete from './autocomplete';
import ChatInputHistory from './history';
@@ -41,6 +42,7 @@ import ChatStore from './store';
import Settings from './settings';
import ChatWindow from './window';
import { ChatPoll, parseQuestionAndTime } from './poll';
+import { CommandMenuPoll } from './command-menus';
import { isMuteActive, MutedTimer } from './mutedtimer';
import EmoteService from './emotes';
import UserFeatures from './features';
@@ -92,14 +94,17 @@ class Chat {
this.flairsMap = new Map();
this.emoteService = new EmoteService();
+ this.icons = new IconsController();
+
this.user = new ChatUser();
this.users = new Map();
this.whispers = new Map();
this.windows = new Map();
this.settings = new Map(settingsdefault);
this.commands = new ChatCommands();
- this.autocomplete = new ChatAutoComplete();
+ this.autocomplete = null;
this.menus = new Map();
+ this.commandMenus = new Map();
this.taggednicks = new Map();
this.taggednotes = new Map();
this.ignoring = new Set();
@@ -291,6 +296,8 @@ class Chat {
this.ui.prependTo(appendTo || 'body');
$(MessageTemplateHTML).prependTo('body');
+ this.icons.renderIcons();
+
// We use this style sheet to apply GUI updates via css (e.g. user focus)
this.css = (() => {
const link = document.createElement('style');
@@ -317,6 +324,7 @@ class Chat {
this.loginscrn = this.ui.find('#chat-login-screen');
this.loadingscrn = this.ui.find('#chat-loading');
this.windowselect = this.ui.find('#chat-windows-select');
+ this.autocomplete = new ChatAutoComplete(this);
this.inputhistory = new ChatInputHistory(this);
this.userfocus = new ChatUserFocus(this, this.css);
this.mainwindow = new ChatWindow('main').into(this);
@@ -392,6 +400,11 @@ class Chat {
);
this.menus.set('event-action-menu', eventActionMenu);
+ this.commandMenus.set(
+ 'poll',
+ new CommandMenuPoll(this.ui.find('#command-menu-poll'), this),
+ );
+
this.autocomplete.bind(this);
// Chat input
@@ -407,7 +420,6 @@ class Chat {
e.stopPropagation();
this.control.emit('SEND', this.input.val().toString().trim());
this.adjustInputHeight();
- this.input.focus();
}
});
@@ -1674,19 +1686,8 @@ class Chat {
}
cmdPOLL(parts, command) {
- const slashCommand = `/${command.toLowerCase()}`;
const textOnly = parts.join(' ');
- try {
- // Assume the command's format is invalid if an exception is thrown.
- parseQuestionAndTime(textOnly);
- } catch {
- MessageBuilder.info(
- `Usage: ${slashCommand} ?