Rewrote code for the input box.
This commit is contained in:
parent
cd16a5d1b5
commit
46549845b9
24 changed files with 483 additions and 584 deletions
|
@ -5,12 +5,12 @@
|
||||||
|
|
||||||
.input__main {
|
.input__main {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 40px;
|
|
||||||
max-height: 140px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input__text {
|
.input__text {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
height: 40px;
|
||||||
|
max-height: 140px;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,37 +32,15 @@
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
.input__button:before {
|
|
||||||
font-family: "Font Awesome 5 Free";
|
|
||||||
font-weight: 900
|
|
||||||
}
|
|
||||||
|
|
||||||
.input__button--markup:before {
|
|
||||||
content: "\f121"
|
|
||||||
}
|
|
||||||
|
|
||||||
.input__button--emotes:before {
|
|
||||||
content: "\f118"
|
|
||||||
}
|
|
||||||
|
|
||||||
.input__button--upload:before {
|
|
||||||
content: "\f574"
|
|
||||||
}
|
|
||||||
|
|
||||||
.input__button--send:before {
|
|
||||||
content: "\f1d8"
|
|
||||||
}
|
|
||||||
|
|
||||||
.input__menus {
|
.input__menus {
|
||||||
max-height: 100px;
|
|
||||||
overflow: auto
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input__menu {
|
.input__menu {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.input__menu:empty {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 1px
|
|
||||||
}
|
|
||||||
|
|
||||||
.input__menu--active {
|
|
||||||
display: block
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ a:hover {
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
visibility: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sjis {
|
.sjis {
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
.markup__button {
|
.markup__button {
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font-family: Verdana, Tahoma, Geneva, Arial, Helvetica, sans-serif;
|
font-family: inherit;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
margin: 2px;
|
transition: background .1s;
|
||||||
transition: background .1s
|
min-width: 26px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markup__link {
|
.markup__link {
|
||||||
color: #1e90ff;
|
color: #1e90ff;
|
||||||
text-decoration: none
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markup__link:hover,
|
.markup__link:hover,
|
||||||
.markup__link:focus {
|
.markup__link:focus {
|
||||||
text-decoration: underline
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat:not(.mami-do-not-mark-links-as-visited) .markup__link--visited,
|
.chat:not(.mami-do-not-mark-links-as-visited) .markup__link--visited,
|
||||||
|
|
30
src/mami.js/chatform/form.jsx
Normal file
30
src/mami.js/chatform/form.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#include chatform/input.jsx
|
||||||
|
#include chatform/markup.jsx
|
||||||
|
|
||||||
|
const MamiChatForm = function(eventTarget) {
|
||||||
|
const input = new MamiChatFormInput(eventTarget);
|
||||||
|
const markup = new MamiChatFormMarkup;
|
||||||
|
|
||||||
|
const html = <form class="input" onsubmit={ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const text = input.text;
|
||||||
|
input.text = '';
|
||||||
|
input.updateGrowHeight();
|
||||||
|
|
||||||
|
eventTarget.dispatch('send', { text: text });
|
||||||
|
}}>
|
||||||
|
{markup}
|
||||||
|
{input}
|
||||||
|
</form>;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get element() { return html; },
|
||||||
|
get markup() { return markup; },
|
||||||
|
get input() { return input; },
|
||||||
|
|
||||||
|
focus: () => {
|
||||||
|
input.focus();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
174
src/mami.js/chatform/input.jsx
Normal file
174
src/mami.js/chatform/input.jsx
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
#include emotes.js
|
||||||
|
#include users.js
|
||||||
|
|
||||||
|
const MamiChatFormInput = function(eventTarget) {
|
||||||
|
const textElem = <textarea class="input__text" name="text" autofocus="autofocus"/>;
|
||||||
|
let submitButton;
|
||||||
|
|
||||||
|
const html = <div class="input__main">
|
||||||
|
{textElem}
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
const createButton = (info, submit) => {
|
||||||
|
const button = <button class="input__button" title={info.title}>
|
||||||
|
<i class={info.icon} />
|
||||||
|
</button>;
|
||||||
|
|
||||||
|
if(submit) {
|
||||||
|
button.type = 'submit';
|
||||||
|
button.classList.add('input__button--send');
|
||||||
|
} else {
|
||||||
|
button.type = 'button';
|
||||||
|
button.onclick = ev => { info.onclick(ev, button, info); };
|
||||||
|
}
|
||||||
|
|
||||||
|
html.insertBefore(button, submitButton);
|
||||||
|
if(submit)
|
||||||
|
submitButton = button;
|
||||||
|
};
|
||||||
|
|
||||||
|
createButton({
|
||||||
|
title: 'Send',
|
||||||
|
icon: 'fas fa-paper-plane',
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
let growInputField = false;
|
||||||
|
let growLastHeight, growBaseHeight;
|
||||||
|
const updateGrowHeight = () => {
|
||||||
|
if(growBaseHeight === undefined)
|
||||||
|
growBaseHeight = textElem.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
let height = growBaseHeight;
|
||||||
|
if(growInputField && textElem.scrollHeight > textElem.clientHeight)
|
||||||
|
height = textElem.scrollHeight;
|
||||||
|
|
||||||
|
textElem.style.height = height > growBaseHeight ? `${height}px` : null;
|
||||||
|
height = textElem.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
if(growLastHeight !== height) {
|
||||||
|
growLastHeight = height;
|
||||||
|
eventTarget.dispatch('resize', {
|
||||||
|
height: height,
|
||||||
|
baseHeight: growBaseHeight,
|
||||||
|
diffHeight: height - growBaseHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
textElem.addEventListener('keyup', () => { updateGrowHeight(); });
|
||||||
|
|
||||||
|
let newLineOnEnter = false;
|
||||||
|
textElem.addEventListener('keydown', ev => {
|
||||||
|
if(ev.key === 'Tab' && !ev.shiftKey && !ev.ctrlKey) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const text = textElem.value;
|
||||||
|
if(text.length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const start = textElem.selectionStart;
|
||||||
|
let position = start;
|
||||||
|
let snippet = '';
|
||||||
|
while(position >= 0 && text.charAt(position - 1) !== ' ' && text.charAt(position - 1) !== "\n") {
|
||||||
|
--position;
|
||||||
|
snippet = text.charAt(position) + snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let insertText;
|
||||||
|
|
||||||
|
if(snippet.indexOf(':') === 0) {
|
||||||
|
let emoteRank = 0;
|
||||||
|
if(Umi.User.hasCurrentUser())
|
||||||
|
emoteRank = Umi.User.getCurrentUser().perms.rank;
|
||||||
|
const emotes = MamiEmotes.findByName(emoteRank, snippet.substring(1), true);
|
||||||
|
if(emotes.length > 0)
|
||||||
|
insertText = `:${emotes[0]}:`;
|
||||||
|
} else {
|
||||||
|
const users = Umi.Users.Find(snippet);
|
||||||
|
if(users.length === 1)
|
||||||
|
insertText = users[0].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(insertText !== undefined) {
|
||||||
|
const nextPos = start - snippet.length + insertText.length;
|
||||||
|
textElem.value = text.slice(0, start - snippet.length) + insertText + text.slice(start);
|
||||||
|
textElem.selectionEnd = textElem.selectionStart = nextPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ev.key === 'Enter' && ev.shiftKey === newLineOnEnter) {
|
||||||
|
ev.preventDefault();
|
||||||
|
textElem.form.requestSubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
textElem.addEventListener('paste', ev => {
|
||||||
|
if(ev.clipboardData?.files.length > 0)
|
||||||
|
eventTarget.dispatch('upload', { files: ev.clipboardData.files });
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
get element() { return html; },
|
||||||
|
|
||||||
|
get text() { return textElem.value; },
|
||||||
|
set text(value) {
|
||||||
|
textElem.value = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
get newLineOnEnter() { return newLineOnEnter; },
|
||||||
|
set newLineOnEnter(value) {
|
||||||
|
newLineOnEnter = !!value;
|
||||||
|
},
|
||||||
|
|
||||||
|
get growInputField() { return growInputField; },
|
||||||
|
set growInputField(value) {
|
||||||
|
growInputField = !!value;
|
||||||
|
},
|
||||||
|
|
||||||
|
createButton: info => createButton(info),
|
||||||
|
updateGrowHeight: updateGrowHeight,
|
||||||
|
|
||||||
|
setText: text => { // this one also focusses!!
|
||||||
|
if(typeof text !== 'string')
|
||||||
|
throw 'text must be string';
|
||||||
|
|
||||||
|
textElem.value = text;
|
||||||
|
textElem.focus();
|
||||||
|
},
|
||||||
|
insertAtCursor: text => {
|
||||||
|
if(typeof text !== 'string')
|
||||||
|
throw 'text must be string';
|
||||||
|
|
||||||
|
const value = textElem.value;
|
||||||
|
let start = textElem.selectionStart;
|
||||||
|
let end = textElem.selectionEnd;
|
||||||
|
|
||||||
|
textElem.value = value.slice(0, start) + text + value.slice(end);
|
||||||
|
textElem.selectionEnd = textElem.selectionStart = start + text.length;
|
||||||
|
textElem.focus();
|
||||||
|
},
|
||||||
|
insertAroundSelection: (before = '', after = '') => {
|
||||||
|
if(typeof before !== 'string')
|
||||||
|
throw 'before must be string';
|
||||||
|
if(typeof after !== 'string')
|
||||||
|
throw 'after must be string';
|
||||||
|
|
||||||
|
const start = textElem.selectionStart;
|
||||||
|
const end = textElem.selectionEnd;
|
||||||
|
const length = end - start;
|
||||||
|
const value = textElem.value;
|
||||||
|
|
||||||
|
textElem.value = value.slice(0, start) + before + value.slice(start, end) + after + value.slice(end);
|
||||||
|
textElem.selectionStart = start + before.length;
|
||||||
|
textElem.selectionEnd = end + before.length;
|
||||||
|
textElem.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
focus: () => {
|
||||||
|
textElem.focus();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
19
src/mami.js/chatform/markup.jsx
Normal file
19
src/mami.js/chatform/markup.jsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const MamiChatFormMarkup = function(input) {
|
||||||
|
const buttons = <div class="input__menu"/>;
|
||||||
|
const html = <div class="input__menus">{buttons}</div>;
|
||||||
|
|
||||||
|
const createButton = info => {
|
||||||
|
buttons.appendChild(<button class="markup__button" type="button" style={info.style} onclick={ev => { info.onclick(ev, info); }}>{info.title}</button>);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
get element() { return html; },
|
||||||
|
|
||||||
|
get visible() { return !html.classList.contains('hidden'); },
|
||||||
|
set visible(state) { html.classList.toggle('hidden', !state); },
|
||||||
|
|
||||||
|
get height() { return html.getBoundingClientRect().height; },
|
||||||
|
|
||||||
|
createButton: createButton,
|
||||||
|
};
|
||||||
|
};
|
|
@ -170,12 +170,15 @@ const MamiColourPicker = function(options) {
|
||||||
dialog: pos => {
|
dialog: pos => {
|
||||||
html.classList.remove('hidden');
|
html.classList.remove('hidden');
|
||||||
|
|
||||||
if(pos instanceof MouseEvent) {
|
if(pos instanceof MouseEvent)
|
||||||
const ev = pos;
|
pos = pos.target;
|
||||||
|
if(pos instanceof Element)
|
||||||
|
pos = pos.getBoundingClientRect();
|
||||||
|
if(pos instanceof DOMRect) {
|
||||||
|
const bbb = pos;
|
||||||
pos = {};
|
pos = {};
|
||||||
|
|
||||||
const mbb = html.getBoundingClientRect();
|
const mbb = html.getBoundingClientRect();
|
||||||
const bbb = ev.target.getBoundingClientRect();
|
|
||||||
const pbb = html.parentNode.getBoundingClientRect();
|
const pbb = html.parentNode.getBoundingClientRect();
|
||||||
|
|
||||||
pos.left = bbb.left;
|
pos.left = bbb.left;
|
||||||
|
|
|
@ -116,12 +116,15 @@ const MamiEmotePicker = function(args) {
|
||||||
|
|
||||||
html.classList.remove('hidden');
|
html.classList.remove('hidden');
|
||||||
|
|
||||||
if(pos instanceof MouseEvent) {
|
if(pos instanceof MouseEvent)
|
||||||
const ev = pos;
|
pos = pos.target;
|
||||||
|
if(pos instanceof Element)
|
||||||
|
pos = pos.getBoundingClientRect();
|
||||||
|
if(pos instanceof DOMRect) {
|
||||||
|
const bbb = pos;
|
||||||
pos = {};
|
pos = {};
|
||||||
|
|
||||||
const mbb = html.getBoundingClientRect();
|
const mbb = html.getBoundingClientRect();
|
||||||
const bbb = ev.target.getBoundingClientRect();
|
|
||||||
const pbb = html.parentNode.getBoundingClientRect();
|
const pbb = html.parentNode.getBoundingClientRect();
|
||||||
|
|
||||||
pos.right = pbb.width - bbb.left;
|
pos.right = pbb.width - bbb.left;
|
||||||
|
|
|
@ -11,6 +11,7 @@ window.Umi = { UI: {} };
|
||||||
#include events.js
|
#include events.js
|
||||||
#include mobile.js
|
#include mobile.js
|
||||||
#include mszauth.js
|
#include mszauth.js
|
||||||
|
#include parsing.js
|
||||||
#include themes.js
|
#include themes.js
|
||||||
#include txtrigs.js
|
#include txtrigs.js
|
||||||
#include uniqstr.js
|
#include uniqstr.js
|
||||||
|
@ -19,6 +20,8 @@ window.Umi = { UI: {} };
|
||||||
#include weeb.js
|
#include weeb.js
|
||||||
#include worker.js
|
#include worker.js
|
||||||
#include audio/autoplay.js
|
#include audio/autoplay.js
|
||||||
|
#include chatform/form.jsx
|
||||||
|
#include colpick/picker.jsx
|
||||||
#include controls/msgbox.jsx
|
#include controls/msgbox.jsx
|
||||||
#include controls/ping.jsx
|
#include controls/ping.jsx
|
||||||
#include controls/views.js
|
#include controls/views.js
|
||||||
|
@ -46,11 +49,7 @@ window.Umi = { UI: {} };
|
||||||
#include sound/sndtest.jsx
|
#include sound/sndtest.jsx
|
||||||
#include ui/chat-layout.js
|
#include ui/chat-layout.js
|
||||||
#include ui/emotes.js
|
#include ui/emotes.js
|
||||||
#include ui/hooks.js
|
|
||||||
#include ui/input-menus.js
|
|
||||||
#include ui/loading-overlay.jsx
|
#include ui/loading-overlay.jsx
|
||||||
#include ui/markup.js
|
|
||||||
#include ui/view.js
|
|
||||||
|
|
||||||
const MamiInit = async args => {
|
const MamiInit = async args => {
|
||||||
args = MamiArgs('args', args, define => {
|
args = MamiArgs('args', args, define => {
|
||||||
|
@ -152,6 +151,7 @@ const MamiInit = async args => {
|
||||||
settings.define('newLineOnEnter').default(false).create();
|
settings.define('newLineOnEnter').default(false).create();
|
||||||
settings.define('keepEmotePickerOpen').default(true).create();
|
settings.define('keepEmotePickerOpen').default(true).create();
|
||||||
settings.define('doNotMarkLinksAsVisited').default(false).create();
|
settings.define('doNotMarkLinksAsVisited').default(false).create();
|
||||||
|
settings.define('showMarkupSelector').type(['always', 'focus', 'never']).default('always').create();
|
||||||
|
|
||||||
const noNotifSupport = !('Notification' in window);
|
const noNotifSupport = !('Notification' in window);
|
||||||
settings.define('enableNotifications').default(false).immutable(noNotifSupport).critical().create();
|
settings.define('enableNotifications').default(false).immutable(noNotifSupport).critical().create();
|
||||||
|
@ -287,10 +287,56 @@ const MamiInit = async args => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const layout = new Umi.UI.ChatLayout(sidebar);
|
|
||||||
|
const chatForm = new MamiChatForm(ctx.events.scopeTo('form'));
|
||||||
|
|
||||||
|
window.addEventListener('keydown', ev => {
|
||||||
|
if((ev.ctrlKey && ev.key !== 'v') || ev.altKey)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(!ev.target.matches('input, textarea, select, button'))
|
||||||
|
chatForm.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
settings.watch('showMarkupSelector', ev => {
|
||||||
|
chatForm.markup.visible = ev.detail.value !== 'never';
|
||||||
|
Umi.UI.Messages.ScrollIfNeeded(chatForm.markup.height);
|
||||||
|
});
|
||||||
|
|
||||||
|
let colourPicker, colourPickerVisible = false;
|
||||||
|
for(const bbCode of UmiBBCodes)
|
||||||
|
if(bbCode.text !== undefined)
|
||||||
|
chatForm.markup.createButton({
|
||||||
|
title: bbCode.text,
|
||||||
|
style: bbCode.style,
|
||||||
|
onclick: ev => {
|
||||||
|
if(bbCode.tag === 'color') {
|
||||||
|
if(colourPicker === undefined) {
|
||||||
|
colourPicker = new MamiColourPicker({ presets: futami.get('colours') });
|
||||||
|
layout.getElement().appendChild(colourPicker.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(colourPickerVisible) {
|
||||||
|
colourPicker.close();
|
||||||
|
} else {
|
||||||
|
colourPickerVisible = true;
|
||||||
|
colourPicker.dialog(ev)
|
||||||
|
.then(colour => { chatForm.input.insertAroundSelection(`[${bbCode.tag}=${MamiColour.hex(colour)}]`, `[/${bbCode.tag}]`) })
|
||||||
|
.catch(() => {}) // noop so the console stops screaming
|
||||||
|
.finally(() => colourPickerVisible = false);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
chatForm.input.insertAroundSelection(`[${bbCode.tag}${(bbCode.arg ? '=' : '')}]`, `[/${bbCode.tag}]`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const layout = new Umi.UI.ChatLayout(chatForm, sidebar);
|
||||||
await ctx.views.unshift(layout);
|
await ctx.views.unshift(layout);
|
||||||
|
|
||||||
Umi.UI.Hooks.AddHooks();
|
ctx.events.watch('form:resize', ev => {
|
||||||
|
Umi.UI.Messages.ScrollIfNeeded(ev.detail.diffHeight);
|
||||||
|
});
|
||||||
|
|
||||||
settings.watch('style', ev => {
|
settings.watch('style', ev => {
|
||||||
for(const className of layout.getElement().classList)
|
for(const className of layout.getElement().classList)
|
||||||
|
@ -306,6 +352,8 @@ const MamiInit = async args => {
|
||||||
});
|
});
|
||||||
settings.watch('preventOverflow', ev => { args.parent.classList.toggle('prevent-overflow', ev.detail.value); });
|
settings.watch('preventOverflow', ev => { args.parent.classList.toggle('prevent-overflow', ev.detail.value); });
|
||||||
settings.watch('doNotMarkLinksAsVisited', ev => { layout.getInterface().getMessageList().getElement().classList.toggle('mami-do-not-mark-links-as-visited', ev.detail.value); });
|
settings.watch('doNotMarkLinksAsVisited', ev => { layout.getInterface().getMessageList().getElement().classList.toggle('mami-do-not-mark-links-as-visited', ev.detail.value); });
|
||||||
|
settings.watch('newLineOnEnter', ev => { chatForm.input.newLineOnEnter = ev.detail.value; });
|
||||||
|
settings.watch('expandTextBox', ev => { chatForm.input.growInputField = ev.detail.value; });
|
||||||
|
|
||||||
settings.watch('minecraft', ev => {
|
settings.watch('minecraft', ev => {
|
||||||
if(ev.detail.initial && ev.detail.value === 'no')
|
if(ev.detail.initial && ev.detail.value === 'no')
|
||||||
|
@ -363,7 +411,7 @@ const MamiInit = async args => {
|
||||||
loadingOverlay.message = 'Building menus...';
|
loadingOverlay.message = 'Building menus...';
|
||||||
|
|
||||||
MamiCompat('Umi.Parser.SockChatBBcode.EmbedStub', { value: () => {} }); // intentionally a no-op
|
MamiCompat('Umi.Parser.SockChatBBcode.EmbedStub', { value: () => {} }); // intentionally a no-op
|
||||||
MamiCompat('Umi.UI.View.SetText', { value: text => console.log(`Umi.UI.View.SetText(text: ${text})`) });
|
MamiCompat('Umi.UI.View.SetText', { value: text => { chatForm.input.setText(text); } });
|
||||||
|
|
||||||
const sbUsers = new MamiSidebarPanelUsers;
|
const sbUsers = new MamiSidebarPanelUsers;
|
||||||
sidebar.createPanel(sbUsers);
|
sidebar.createPanel(sbUsers);
|
||||||
|
@ -377,13 +425,13 @@ const MamiInit = async args => {
|
||||||
name: 'action',
|
name: 'action',
|
||||||
text: 'Describe action',
|
text: 'Describe action',
|
||||||
condition: entry => Umi.User.getCurrentUser()?.id === entry.id,
|
condition: entry => Umi.User.getCurrentUser()?.id === entry.id,
|
||||||
onclick: entry => { Umi.UI.View.SetText('/me '); },
|
onclick: entry => { chatForm.input.setText('/me '); },
|
||||||
});
|
});
|
||||||
sbUsers.addOption({
|
sbUsers.addOption({
|
||||||
name: 'nick',
|
name: 'nick',
|
||||||
text: 'Set nickname',
|
text: 'Set nickname',
|
||||||
condition: entry => Umi.User.getCurrentUser()?.id === entry.id && Umi.User.getCurrentUser().perms.canSetNick,
|
condition: entry => Umi.User.getCurrentUser()?.id === entry.id && Umi.User.getCurrentUser().perms.canSetNick,
|
||||||
onclick: entry => { Umi.UI.View.SetText('/nick '); },
|
onclick: entry => { chatForm.input.setText('/nick '); },
|
||||||
});
|
});
|
||||||
sbUsers.addOption({
|
sbUsers.addOption({
|
||||||
name: 'bans',
|
name: 'bans',
|
||||||
|
@ -415,13 +463,13 @@ const MamiInit = async args => {
|
||||||
name: 'dm',
|
name: 'dm',
|
||||||
text: 'Send direct message',
|
text: 'Send direct message',
|
||||||
condition: entry => Umi.User.getCurrentUser()?.id !== entry.id,
|
condition: entry => Umi.User.getCurrentUser()?.id !== entry.id,
|
||||||
onclick: entry => { Umi.UI.View.SetText(`/msg ${entry.name} `); },
|
onclick: entry => { chatForm.input.setText(`/msg ${entry.name} `); },
|
||||||
});
|
});
|
||||||
sbUsers.addOption({
|
sbUsers.addOption({
|
||||||
name: 'kick',
|
name: 'kick',
|
||||||
text: 'Kick from chat',
|
text: 'Kick from chat',
|
||||||
condition: entry => Umi.User.hasCurrentUser() && Umi.User.getCurrentUser().id !== entry.id && Umi.User.getCurrentUser().perms.canKick,
|
condition: entry => Umi.User.hasCurrentUser() && Umi.User.getCurrentUser().id !== entry.id && Umi.User.getCurrentUser().perms.canKick,
|
||||||
onclick: entry => { Umi.UI.View.SetText(`/kick ${entry.name} `); },
|
onclick: entry => { chatForm.input.setText(`/kick ${entry.name} `); },
|
||||||
});
|
});
|
||||||
sbUsers.addOption({
|
sbUsers.addOption({
|
||||||
name: 'ipaddr',
|
name: 'ipaddr',
|
||||||
|
@ -462,6 +510,7 @@ const MamiInit = async args => {
|
||||||
category.setting('autoEmbedPlay').title('Auto-play embedded media').done();
|
category.setting('autoEmbedPlay').title('Auto-play embedded media').done();
|
||||||
category.setting('keepEmotePickerOpen').title('Keep emoticon picker open').done();
|
category.setting('keepEmotePickerOpen').title('Keep emoticon picker open').done();
|
||||||
category.setting('newLineOnEnter').title('Swap Enter and Shift+Enter behaviour').done();
|
category.setting('newLineOnEnter').title('Swap Enter and Shift+Enter behaviour').done();
|
||||||
|
category.setting('showMarkupSelector').title('Show markup buttons').type('checkbox').on('always').off('never').done();
|
||||||
});
|
});
|
||||||
sbSettings.category(category => {
|
sbSettings.category(category => {
|
||||||
category.header('Notifications');
|
category.header('Notifications');
|
||||||
|
@ -660,36 +709,35 @@ const MamiInit = async args => {
|
||||||
const sbActPing = new MamiSidebarActionPing(pingIndicator, ctx.msgbox);
|
const sbActPing = new MamiSidebarActionPing(pingIndicator, ctx.msgbox);
|
||||||
sidebar.createAction(sbActPing);
|
sidebar.createAction(sbActPing);
|
||||||
|
|
||||||
Umi.UI.InputMenus.Add('markup');
|
|
||||||
Umi.UI.Markup.SetPickerTarget(layout.getElement());
|
|
||||||
|
|
||||||
let emotePicker, emotePickerVisible = false;
|
let emotePicker, emotePickerVisible = false;
|
||||||
Umi.UI.InputMenus.AddButton('emotes', 'Emoticons', ev => {
|
chatForm.input.createButton({
|
||||||
if(emotePicker === undefined) {
|
title: 'Emoticons',
|
||||||
emotePicker = new MamiEmotePicker({
|
icon: 'fas fa-smile',
|
||||||
onClose: () => { emotePickerVisible = false; },
|
onclick: (ev, button) => {
|
||||||
onPick: emote => {
|
if(emotePicker === undefined) {
|
||||||
const emoteStr = `:${emote.strings[0]}:`;
|
emotePicker = new MamiEmotePicker({
|
||||||
Umi.UI.View.EnterAtCursor(emoteStr);
|
onClose: () => { emotePickerVisible = false; },
|
||||||
Umi.UI.View.SetPosition(Umi.UI.View.GetPosition() + emoteStr.length);
|
onPick: emote => {
|
||||||
Umi.UI.View.SetPosition(Umi.UI.View.GetPosition(), true);
|
chatForm.input.insertAtCursor(`:${emote.strings[0]}:`);
|
||||||
Umi.UI.View.Focus();
|
},
|
||||||
},
|
getEmotes: () => MamiEmotes.all(Umi.User.getCurrentUser().perms.rank),
|
||||||
getEmotes: () => MamiEmotes.all(Umi.User.getCurrentUser().perms.rank),
|
setKeepOpenOnPick: value => { settings.set('keepEmotePickerOpen', value); },
|
||||||
setKeepOpenOnPick: value => { settings.set('keepEmotePickerOpen', value); },
|
});
|
||||||
});
|
layout.getElement().appendChild(emotePicker.element);
|
||||||
layout.getElement().appendChild(emotePicker.element);
|
settings.watch('keepEmotePickerOpen', ev => { emotePicker.keepOpenOnPick = ev.detail.value; });
|
||||||
settings.watch('keepEmotePickerOpen', ev => { emotePicker.keepOpenOnPick = ev.detail.value; });
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(emotePickerVisible) {
|
if(emotePickerVisible) {
|
||||||
emotePicker.close();
|
emotePicker.close();
|
||||||
} else {
|
} else {
|
||||||
emotePickerVisible = true;
|
emotePickerVisible = true;
|
||||||
emotePicker.dialog(ev);
|
emotePicker.dialog(button);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let doUpload;
|
let doUpload;
|
||||||
ctx.eeprom = new MamiEEPROM(futami.get('eeprom2'), MamiMisuzuAuth.getLine);
|
ctx.eeprom = new MamiEEPROM(futami.get('eeprom2'), MamiMisuzuAuth.getLine);
|
||||||
ctx.eeprom.init()
|
ctx.eeprom.init()
|
||||||
|
@ -718,7 +766,7 @@ const MamiInit = async args => {
|
||||||
} else
|
} else
|
||||||
text = location.protocol + upload.url;
|
text = location.protocol + upload.url;
|
||||||
|
|
||||||
Umi.UI.Markup.InsertRaw(text, '')
|
chatForm.input.insertAtCursor(text);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
sbUploads.addOption({
|
sbUploads.addOption({
|
||||||
|
@ -784,13 +832,17 @@ const MamiInit = async args => {
|
||||||
});
|
});
|
||||||
args.parent.appendChild(uploadForm);
|
args.parent.appendChild(uploadForm);
|
||||||
|
|
||||||
Umi.UI.InputMenus.AddButton('upload', 'Upload', () => uploadForm.click(), 'markup');
|
chatForm.input.createButton({
|
||||||
|
title: 'Upload',
|
||||||
|
icon: 'fas fa-file-upload',
|
||||||
|
onclick: () => { uploadForm.click(); },
|
||||||
|
});
|
||||||
|
|
||||||
$i('umi-msg-text').onpaste = ev => {
|
ctx.events.watch('form:upload', ev => {
|
||||||
if(ev.clipboardData && ev.clipboardData.files.length > 0)
|
console.info(ev);
|
||||||
for(const file of ev.clipboardData.files)
|
for(const file of ev.detail.files)
|
||||||
doUpload(file);
|
doUpload(file);
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// figure out how to display a UI for this someday
|
// figure out how to display a UI for this someday
|
||||||
|
@ -860,6 +912,10 @@ const MamiInit = async args => {
|
||||||
const conMan = new MamiConnectionManager(sockChat, settings, futami.get('servers'), ctx.events.scopeTo('conn'));
|
const conMan = new MamiConnectionManager(sockChat, settings, futami.get('servers'), ctx.events.scopeTo('conn'));
|
||||||
ctx.conMan = conMan;
|
ctx.conMan = conMan;
|
||||||
|
|
||||||
|
ctx.events.watch('form:send', ev => {
|
||||||
|
sockChat.client.sendMessage(ev.detail.text);
|
||||||
|
});
|
||||||
|
|
||||||
sbChannels.onClickEntry = async info => {
|
sbChannels.onClickEntry = async info => {
|
||||||
await sockChat.client.switchChannel(info);
|
await sockChat.client.switchChannel(info);
|
||||||
|
|
||||||
|
|
|
@ -1,91 +1,93 @@
|
||||||
#include utility.js
|
#include utility.js
|
||||||
#include ui/markup.js
|
|
||||||
|
|
||||||
if(!Umi.Parser) Umi.Parser = {};
|
if(!Umi.Parser) Umi.Parser = {};
|
||||||
if(!Umi.Parser.SockChatBBcode) Umi.Parser.SockChatBBcode = {};
|
if(!Umi.Parser.SockChatBBcode) Umi.Parser.SockChatBBcode = {};
|
||||||
|
|
||||||
Umi.Parsing = (function() {
|
const UmiBBCodes = [
|
||||||
const bbCodes = [
|
{
|
||||||
{
|
tag: 'b',
|
||||||
tag: 'b',
|
text: 'B',
|
||||||
replace: '<b>{0}</b>',
|
style: 'font-weight: 700',
|
||||||
button: true,
|
replace: '<b>{0}</b>',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: 'i',
|
tag: 'i',
|
||||||
replace: '<i>{0}</i>',
|
text: 'I',
|
||||||
button: true,
|
style: 'font-style: italic',
|
||||||
},
|
replace: '<i>{0}</i>',
|
||||||
{
|
},
|
||||||
tag: 'u',
|
{
|
||||||
replace: '<u>{0}</u>',
|
tag: 'u',
|
||||||
button: true,
|
text: 'U',
|
||||||
},
|
style: 'text-decoration: underline',
|
||||||
{
|
replace: '<u>{0}</u>',
|
||||||
tag: 's',
|
},
|
||||||
replace: '<del>{0}</del>',
|
{
|
||||||
button: true,
|
tag: 's',
|
||||||
},
|
text: 'S',
|
||||||
{
|
style: 'text-decoration: line-through',
|
||||||
tag: 'quote',
|
replace: '<del>{0}</del>',
|
||||||
replace: '<q style="font-variant: small-caps;">{0}</q>',
|
},
|
||||||
button: true,
|
{
|
||||||
},
|
tag: 'quote',
|
||||||
{
|
text: 'Quote',
|
||||||
tag: 'code',
|
replace: '<q style="font-variant: small-caps;">{0}</q>',
|
||||||
replace: '<span style="white-space: pre-wrap; font-family: monospace;">{0}</span>',
|
},
|
||||||
button: true,
|
{
|
||||||
},
|
tag: 'code',
|
||||||
{
|
text: 'Code',
|
||||||
tag: 'sjis',
|
replace: '<span style="white-space: pre-wrap; font-family: monospace;">{0}</span>',
|
||||||
replace: '<span class="sjis" style="white-space: pre-wrap;">{0}</span>',
|
},
|
||||||
},
|
{
|
||||||
{
|
tag: 'sjis',
|
||||||
tag: 'color',
|
replace: '<span class="sjis" style="white-space: pre-wrap;">{0}</span>',
|
||||||
hasArg: true,
|
},
|
||||||
stripArg: ';:{}<>&|\\/~\'"',
|
{
|
||||||
replace: '<span style="color:{0};">{1}</span>',
|
tag: 'color',
|
||||||
isToggle: true,
|
text: 'Colour',
|
||||||
button: true,
|
hasArg: true,
|
||||||
},
|
stripArg: ';:{}<>&|\\/~\'"',
|
||||||
{
|
replace: '<span style="color:{0};">{1}</span>',
|
||||||
tag: 'img',
|
},
|
||||||
stripText: '"\'',
|
{
|
||||||
replace: '<span title="{0}"><span title="link"><a class="markup__link" href="{0}" target="_blank" rel="nofollow noreferrer noopener">{0}</a></span> [<a href="#" onclick="Umi.Parser.SockChatBBcode.EmbedImage(this);return false;" class="markup__link">Embed</a>]</span>',
|
tag: 'img',
|
||||||
button: true,
|
text: 'Image',
|
||||||
},
|
stripText: '"\'',
|
||||||
{
|
replace: '<span title="{0}"><span title="link"><a class="markup__link" href="{0}" target="_blank" rel="nofollow noreferrer noopener">{0}</a></span> [<a href="#" onclick="Umi.Parser.SockChatBBcode.EmbedImage(this);return false;" class="markup__link">Embed</a>]</span>',
|
||||||
tag: 'url',
|
},
|
||||||
stripText: '"\'',
|
{
|
||||||
replace: '<a href="{0}" target="_blank" rel="nofollow noreferrer noopener" class="markup__link">{0}</a>',
|
tag: 'url',
|
||||||
},
|
stripText: '"\'',
|
||||||
{
|
replace: '<a href="{0}" target="_blank" rel="nofollow noreferrer noopener" class="markup__link">{0}</a>',
|
||||||
tag: 'url',
|
},
|
||||||
hasArg: true,
|
{
|
||||||
stripArg: '"\'',
|
tag: 'url',
|
||||||
replace: '<a href="{0}" target="_blank" rel="nofollow noreferrer noopener" class="markup__link">{1}</a>',
|
text: 'URL',
|
||||||
button: true,
|
hasArg: true,
|
||||||
},
|
stripArg: '"\'',
|
||||||
{
|
replace: '<a href="{0}" target="_blank" rel="nofollow noreferrer noopener" class="markup__link">{1}</a>',
|
||||||
tag: 'video',
|
},
|
||||||
stripText: '"\'',
|
{
|
||||||
replace: '<span title="{0}"><span title="link"><a class="markup__link" href="{0}" target="_blank" rel="nofollow noreferrer noopener">{0}</a></span> [<a href="#" onclick="Umi.Parser.SockChatBBcode.EmbedVideo(this);return false;" class="markup__link">Embed</a>]</span>',
|
tag: 'video',
|
||||||
button: true,
|
text: 'Video',
|
||||||
},
|
stripText: '"\'',
|
||||||
{
|
replace: '<span title="{0}"><span title="link"><a class="markup__link" href="{0}" target="_blank" rel="nofollow noreferrer noopener">{0}</a></span> [<a href="#" onclick="Umi.Parser.SockChatBBcode.EmbedVideo(this);return false;" class="markup__link">Embed</a>]</span>',
|
||||||
tag: 'audio',
|
},
|
||||||
stripText: '"\'',
|
{
|
||||||
replace: '<span title="{0}"><span title="link"><a class="markup__link" href="{0}" target="_blank" rel="nofollow noreferrer noopener">{0}</a></span> [<a href="#" onclick="Umi.Parser.SockChatBBcode.EmbedAudio(this);return false;" class="markup__link">Embed</a>]</span>',
|
tag: 'audio',
|
||||||
button: true,
|
text: 'Audio',
|
||||||
},
|
stripText: '"\'',
|
||||||
{
|
replace: '<span title="{0}"><span title="link"><a class="markup__link" href="{0}" target="_blank" rel="nofollow noreferrer noopener">{0}</a></span> [<a href="#" onclick="Umi.Parser.SockChatBBcode.EmbedAudio(this);return false;" class="markup__link">Embed</a>]</span>',
|
||||||
tag: 'spoiler',
|
},
|
||||||
stripText: '"\'',
|
{
|
||||||
replace: '<span data-shit="{0}"><span>*** HIDDEN ***</span> [<a href="#" onclick="Umi.Parser.SockChatBBcode.ToggleSpoiler(this);return false;" class="markup__link">Reveal</a>]</span>',
|
tag: 'spoiler',
|
||||||
button: true,
|
text: 'Spoiler',
|
||||||
}
|
stripText: '"\'',
|
||||||
];
|
replace: '<span data-shit="{0}"><span>*** HIDDEN ***</span> [<a href="#" onclick="Umi.Parser.SockChatBBcode.ToggleSpoiler(this);return false;" class="markup__link">Reveal</a>]</span>',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
Umi.Parsing = (function() {
|
||||||
const replaceAll = function(haystack, needle, replace, ignore) {
|
const replaceAll = function(haystack, needle, replace, ignore) {
|
||||||
return haystack.replace(
|
return haystack.replace(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
|
@ -362,21 +364,9 @@ Umi.Parsing = (function() {
|
||||||
Umi.Parser.SockChatBBcode.ToggleSpoiler = toggleSpoiler;
|
Umi.Parser.SockChatBBcode.ToggleSpoiler = toggleSpoiler;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Init: function() {
|
|
||||||
for (let i = 0; i < bbCodes.length; i++) {
|
|
||||||
const bbCode = bbCodes[i];
|
|
||||||
if(!bbCode.button)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const start = '[{0}]'.replace('{0}', bbCode.tag + (bbCode.arg ? '=' : '')), end = '[/{0}]'.replace('{0}', bbCode.tag);
|
|
||||||
const text = (bbCode.tag.length > 1 ? bbCode.tag.substring(0, 1).toUpperCase() + bbCode.tag.substring(1) : bbCode.tag).replace('Color', 'Colour');
|
|
||||||
|
|
||||||
Umi.UI.Markup.Add(bbCode.tag, text, start, end);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Parse: function(element, message) {
|
Parse: function(element, message) {
|
||||||
for(let i = 0; i < bbCodes.length; i++) {
|
for(let i = 0; i < UmiBBCodes.length; i++) {
|
||||||
const bbCode = bbCodes[i];
|
const bbCode = UmiBBCodes[i];
|
||||||
|
|
||||||
if(!bbCode.hasArg) {
|
if(!bbCode.hasArg) {
|
||||||
let at = 0;
|
let at = 0;
|
||||||
|
|
|
@ -113,6 +113,8 @@ const MamiSidebarPanelSettings = function(settings, msgBox) {
|
||||||
options: undefined,
|
options: undefined,
|
||||||
confirm: undefined,
|
confirm: undefined,
|
||||||
disabled: info.immutable,
|
disabled: info.immutable,
|
||||||
|
trueValue: true,
|
||||||
|
falseValue: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const detectType = () => {
|
const detectType = () => {
|
||||||
|
@ -151,6 +153,20 @@ const MamiSidebarPanelSettings = function(settings, msgBox) {
|
||||||
setting.options = value;
|
setting.options = value;
|
||||||
return pub;
|
return pub;
|
||||||
},
|
},
|
||||||
|
on: value => {
|
||||||
|
if(typeof value === undefined)
|
||||||
|
value = true;
|
||||||
|
|
||||||
|
setting.trueValue = value;
|
||||||
|
return pub;
|
||||||
|
},
|
||||||
|
off: value => {
|
||||||
|
if(typeof value === undefined)
|
||||||
|
value = false;
|
||||||
|
|
||||||
|
setting.falseValue = value;
|
||||||
|
return pub;
|
||||||
|
},
|
||||||
confirm: value => {
|
confirm: value => {
|
||||||
if(!Array.isArray(value) && typeof value !== 'string' && value !== undefined)
|
if(!Array.isArray(value) && typeof value !== 'string' && value !== undefined)
|
||||||
throw 'value must be an array or a string';
|
throw 'value must be an array or a string';
|
||||||
|
@ -200,7 +216,15 @@ const MamiSidebarPanelSettings = function(settings, msgBox) {
|
||||||
label.append(title, input);
|
label.append(title, input);
|
||||||
|
|
||||||
if(setting.type === 'checkbox') {
|
if(setting.type === 'checkbox') {
|
||||||
settings.watch(info.name, ev => input.checked = ev.detail.value);
|
settings.watch(info.name, ev => input.checked = ev.detail.value !== setting.falseValue);
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
settings.set(
|
||||||
|
info.name,
|
||||||
|
settings.get(info.name) !== setting.falseValue ? setting.falseValue : setting.trueValue
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
input.addEventListener('change', () => {
|
input.addEventListener('change', () => {
|
||||||
if(setting.confirm !== undefined && input.checked !== info.fallback) {
|
if(setting.confirm !== undefined && input.checked !== info.fallback) {
|
||||||
msgBox.show({
|
msgBox.show({
|
||||||
|
@ -208,12 +232,12 @@ const MamiSidebarPanelSettings = function(settings, msgBox) {
|
||||||
yes: { primary: false },
|
yes: { primary: false },
|
||||||
no: { primary: true },
|
no: { primary: true },
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
settings.toggle(info.name);
|
toggle();
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
input.checked = info.fallback;
|
input.checked = info.fallback;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
settings.toggle(info.name);
|
toggle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#include compat.js
|
#include compat.js
|
||||||
#include mszauth.js
|
#include mszauth.js
|
||||||
#include ui/hooks.js
|
|
||||||
|
|
||||||
const MamiSockChat = function(protoWorker) {
|
const MamiSockChat = function(protoWorker) {
|
||||||
const events = protoWorker.eventTarget('sockchat');
|
const events = protoWorker.eventTarget('sockchat');
|
||||||
|
@ -23,8 +22,6 @@ const MamiSockChat = function(protoWorker) {
|
||||||
client = await protoWorker.root.create('sockchat', { ping: futami.get('ping') });
|
client = await protoWorker.root.create('sockchat', { ping: futami.get('ping') });
|
||||||
await client.setDumpPackets(dumpPackets);
|
await client.setDumpPackets(dumpPackets);
|
||||||
|
|
||||||
Umi.UI.Hooks.SetCallbacks(client.sendMessage);
|
|
||||||
|
|
||||||
MamiCompat('Umi.Server', { get: () => client, configurable: true });
|
MamiCompat('Umi.Server', { get: () => client, configurable: true });
|
||||||
MamiCompat('Umi.Server.SendMessage', { value: text => client.sendMessage(text), configurable: true });
|
MamiCompat('Umi.Server.SendMessage', { value: text => client.sendMessage(text), configurable: true });
|
||||||
MamiCompat('Umi.Protocol.SockChat.Protocol.Instance.SendMessage', { value: text => client.sendMessage(text), configurable: true });
|
MamiCompat('Umi.Protocol.SockChat.Protocol.Instance.SendMessage', { value: text => client.sendMessage(text), configurable: true });
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include notices/baka.jsx
|
#include notices/baka.jsx
|
||||||
#include sockchat/modal.js
|
#include sockchat/modal.js
|
||||||
#include ui/emotes.js
|
#include ui/emotes.js
|
||||||
#include ui/markup.js
|
|
||||||
#include ui/messages.jsx
|
#include ui/messages.jsx
|
||||||
|
|
||||||
const MamiSockChatHandlers = function(
|
const MamiSockChatHandlers = function(
|
||||||
|
@ -103,9 +102,6 @@ const MamiSockChatHandlers = function(
|
||||||
|
|
||||||
sbUsers.createEntry(ev.detail.user);
|
sbUsers.createEntry(ev.detail.user);
|
||||||
|
|
||||||
Umi.UI.Markup.Reset();
|
|
||||||
Umi.Parsing.Init();
|
|
||||||
|
|
||||||
if(ctx.views.count > 1)
|
if(ctx.views.count > 1)
|
||||||
ctx.views.pop();
|
ctx.views.pop();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
#include utility.js
|
|
||||||
|
|
||||||
Umi.UI.ChatInputMain = function() {
|
|
||||||
const html = $e({
|
|
||||||
attrs: {
|
|
||||||
id: 'umi-msg-container',
|
|
||||||
className: 'input__main',
|
|
||||||
},
|
|
||||||
child: [
|
|
||||||
{
|
|
||||||
tag: 'textarea',
|
|
||||||
attrs: {
|
|
||||||
id: 'umi-msg-text',
|
|
||||||
name: 'text',
|
|
||||||
className: 'input__text',
|
|
||||||
autofocus: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'button',
|
|
||||||
attrs: {
|
|
||||||
id: 'umi-msg-send',
|
|
||||||
className: 'input__button input__button--send',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
getElement: function() {
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
#include utility.js
|
|
||||||
|
|
||||||
Umi.UI.ChatInputMenus = function() {
|
|
||||||
const html = $e({
|
|
||||||
attrs: {
|
|
||||||
id: 'umi-msg-menu',
|
|
||||||
className: 'input__menus',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
getElement: function() {
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,32 +0,0 @@
|
||||||
#include utility.js
|
|
||||||
#include ui/chat-input-main.js
|
|
||||||
#include ui/chat-input-menus.js
|
|
||||||
|
|
||||||
Umi.UI.ChatInput = function() {
|
|
||||||
const menus = new Umi.UI.ChatInputMenus;
|
|
||||||
const main = new Umi.UI.ChatInputMain;
|
|
||||||
|
|
||||||
const html = $e({
|
|
||||||
tag: 'form',
|
|
||||||
attrs: {
|
|
||||||
id: 'umi-msg-form',
|
|
||||||
className: 'input',
|
|
||||||
},
|
|
||||||
child: [
|
|
||||||
menus,
|
|
||||||
main,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
getMenus: function() {
|
|
||||||
return menus;
|
|
||||||
},
|
|
||||||
getMain: function() {
|
|
||||||
return main;
|
|
||||||
},
|
|
||||||
getElement: function() {
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,10 +1,8 @@
|
||||||
#include utility.js
|
#include utility.js
|
||||||
#include ui/chat-message-list.js
|
#include ui/chat-message-list.js
|
||||||
#include ui/chat-input.js
|
|
||||||
|
|
||||||
Umi.UI.ChatInterface = function() {
|
Umi.UI.ChatInterface = function(chatForm) {
|
||||||
const messages = new Umi.UI.ChatMessageList;
|
const messages = new Umi.UI.ChatMessageList;
|
||||||
const input = new Umi.UI.ChatInput;
|
|
||||||
|
|
||||||
const html = $e({
|
const html = $e({
|
||||||
attrs: {
|
attrs: {
|
||||||
|
@ -12,7 +10,7 @@ Umi.UI.ChatInterface = function() {
|
||||||
},
|
},
|
||||||
child: [
|
child: [
|
||||||
messages,
|
messages,
|
||||||
input,
|
chatForm,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,9 +18,6 @@ Umi.UI.ChatInterface = function() {
|
||||||
getMessageList: function() {
|
getMessageList: function() {
|
||||||
return messages;
|
return messages;
|
||||||
},
|
},
|
||||||
getInput: function() {
|
|
||||||
return input;
|
|
||||||
},
|
|
||||||
getElement: function() {
|
getElement: function() {
|
||||||
return html;
|
return html;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#include utility.js
|
#include utility.js
|
||||||
#include ui/chat-interface.js
|
#include ui/chat-interface.js
|
||||||
#include sidebar/sidebar.jsx
|
|
||||||
|
|
||||||
// this needs revising at some point but will suffice for now
|
// this needs revising at some point but will suffice for now
|
||||||
Umi.UI.ChatLayout = function(sideBar) {
|
Umi.UI.ChatLayout = function(chatForm, sideBar) {
|
||||||
const main = new Umi.UI.ChatInterface;
|
const main = new Umi.UI.ChatInterface(chatForm);
|
||||||
|
|
||||||
const html = $e({
|
const html = $e({
|
||||||
attrs: {
|
attrs: {
|
||||||
|
@ -18,9 +17,6 @@ Umi.UI.ChatLayout = function(sideBar) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getSideBar: function() {
|
|
||||||
return sideBar;
|
|
||||||
},
|
|
||||||
getInterface: function() {
|
getInterface: function() {
|
||||||
return main;
|
return main;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
#include users.js
|
|
||||||
#include utility.js
|
|
||||||
#include ui/messages.jsx
|
|
||||||
#include ui/view.js
|
|
||||||
|
|
||||||
Umi.UI.Hooks = (function() {
|
|
||||||
let sendMessage;
|
|
||||||
|
|
||||||
return {
|
|
||||||
SetCallbacks: (sendMessageFunc) => {
|
|
||||||
sendMessage = sendMessageFunc;
|
|
||||||
},
|
|
||||||
AddHooks: function() {
|
|
||||||
const msgForm = $i('umi-msg-form');
|
|
||||||
const msgText = $i('umi-msg-text');
|
|
||||||
|
|
||||||
window.addEventListener('keydown', function(ev) {
|
|
||||||
if((ev.ctrlKey && ev.key !== 'v') || ev.altKey)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(!ev.target.matches('input, textarea, select, button'))
|
|
||||||
msgText.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
msgForm.addEventListener('submit', ev => {
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
if(typeof sendMessage !== 'function')
|
|
||||||
return;
|
|
||||||
|
|
||||||
const textField = ev.target.elements.namedItem('text');
|
|
||||||
if(textField instanceof HTMLTextAreaElement) {
|
|
||||||
let text = textField.value;
|
|
||||||
textField.value = '';
|
|
||||||
|
|
||||||
text = text.replace(/\t/g, ' ');
|
|
||||||
if(text.length > 0)
|
|
||||||
sendMessage(text);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
msgText.addEventListener('keyup', function(ev) {
|
|
||||||
const isScrolledToBottom = Umi.UI.Messages.IsScrolledToBottom();
|
|
||||||
|
|
||||||
const elemParent = msgText.parentNode;
|
|
||||||
let height = 40;
|
|
||||||
|
|
||||||
if(mami.settings.get('expandTextBox') && msgText.scrollHeight > msgText.clientHeight)
|
|
||||||
height = msgText.scrollHeight;
|
|
||||||
|
|
||||||
if(height > 40)
|
|
||||||
elemParent.style.height = height.toString() + 'px';
|
|
||||||
else
|
|
||||||
elemParent.style.height = null;
|
|
||||||
|
|
||||||
if(isScrolledToBottom)
|
|
||||||
Umi.UI.Messages.ScrollIfNeeded();
|
|
||||||
});
|
|
||||||
|
|
||||||
msgText.addEventListener('keydown', function(ev) {
|
|
||||||
if(ev.key === 'Tab' && (!ev.shiftKey || !ev.ctrlKey)) {
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
const text = Umi.UI.View.GetText();
|
|
||||||
if(text.length < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const start = Umi.UI.View.GetPosition();
|
|
||||||
let position = start,
|
|
||||||
snippet = '';
|
|
||||||
while(position >= 0 && text.charAt(position - 1) !== ' ' && text.charAt(position - 1) !== "\n") {
|
|
||||||
--position;
|
|
||||||
snippet = text.charAt(position) + snippet;
|
|
||||||
}
|
|
||||||
|
|
||||||
let insertText = undefined;
|
|
||||||
|
|
||||||
if(snippet.indexOf(':') === 0) {
|
|
||||||
let emoteRank = 0;
|
|
||||||
if(Umi.User.hasCurrentUser())
|
|
||||||
emoteRank = Umi.User.getCurrentUser().perms.rank;
|
|
||||||
const emotes = MamiEmotes.findByName(emoteRank, snippet.substring(1), true);
|
|
||||||
if(emotes.length > 0)
|
|
||||||
insertText = ':' + emotes[0] + ':';
|
|
||||||
} else {
|
|
||||||
const users = Umi.Users.Find(snippet);
|
|
||||||
if(users.length === 1)
|
|
||||||
insertText = users[0].name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(insertText !== undefined) {
|
|
||||||
Umi.UI.View.SetText(text.slice(0, start - snippet.length) + text.slice(start));
|
|
||||||
Umi.UI.View.SetPosition(start - snippet.length);
|
|
||||||
Umi.UI.View.EnterAtCursor(insertText);
|
|
||||||
Umi.UI.View.SetPosition(Umi.UI.View.GetPosition() + insertText.length);
|
|
||||||
Umi.UI.View.SetPosition(Umi.UI.View.GetPosition(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if((ev.key === 'Enter' || ev.key === 'NumpadEnter') && ev.shiftKey === mami.settings.get('newLineOnEnter')) {
|
|
||||||
ev.preventDefault();
|
|
||||||
msgForm.requestSubmit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,68 +0,0 @@
|
||||||
#include utility.js
|
|
||||||
#include ui/input-menus.js
|
|
||||||
|
|
||||||
Umi.UI.InputMenus = (function() {
|
|
||||||
const ids = [];
|
|
||||||
|
|
||||||
const createButtonId = id => `umi-msg-menu-btn-${id}`;
|
|
||||||
const createButton = function(id, title, onClick) {
|
|
||||||
return $e({
|
|
||||||
tag: 'button',
|
|
||||||
attrs: {
|
|
||||||
type: 'button',
|
|
||||||
id: createButtonId(id),
|
|
||||||
classList: ['input__button', 'input__button--' + id],
|
|
||||||
title: title,
|
|
||||||
onclick: onClick,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
Add: function(baseId, title, beforeButtonId) {
|
|
||||||
if(baseId !== 'markup')
|
|
||||||
throw 'only baseId "markup" may be added';
|
|
||||||
|
|
||||||
if(ids.includes(baseId))
|
|
||||||
return;
|
|
||||||
ids.push(baseId);
|
|
||||||
|
|
||||||
let beforeButton;
|
|
||||||
if(typeof beforeButtonId === 'string')
|
|
||||||
beforeButton = $i(createButtonId(beforeButtonId))
|
|
||||||
if(!(beforeButton instanceof Element))
|
|
||||||
beforeButton = $i('umi-msg-send');
|
|
||||||
|
|
||||||
if(typeof title === 'string')
|
|
||||||
$i('umi-msg-container').insertBefore(createButton(baseId, title), beforeButton);
|
|
||||||
$i('umi-msg-menu').appendChild(
|
|
||||||
$e({
|
|
||||||
attrs: {
|
|
||||||
'class': ['input__menu', 'input__menu--' + baseId, 'input__menu--active'],
|
|
||||||
id: 'umi-msg-menu-sub-' + baseId,
|
|
||||||
tabindex: '0',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
AddButton: function(baseId, title, onClick, beforeButtonId) {
|
|
||||||
if(ids.includes(baseId))
|
|
||||||
return;
|
|
||||||
ids.push(baseId);
|
|
||||||
|
|
||||||
let beforeButton;
|
|
||||||
if(typeof beforeButtonId === 'string')
|
|
||||||
beforeButton = $i(createButtonId(beforeButtonId))
|
|
||||||
if(!(beforeButton instanceof Element))
|
|
||||||
beforeButton = $i('umi-msg-send');
|
|
||||||
|
|
||||||
$i('umi-msg-container').insertBefore(createButton(baseId, title, onClick), beforeButton);
|
|
||||||
},
|
|
||||||
Get: function(baseId, button) {
|
|
||||||
const id = 'umi-msg-menu-' + (button ? 'btn' : 'sub') + '-' + baseId;
|
|
||||||
if(ids.indexOf(baseId) >= 0)
|
|
||||||
return $i(id);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,71 +0,0 @@
|
||||||
#include colour.js
|
|
||||||
#include common.js
|
|
||||||
#include utility.js
|
|
||||||
#include colpick/picker.jsx
|
|
||||||
#include ui/input-menus.js
|
|
||||||
#include ui/markup.js
|
|
||||||
#include ui/view.js
|
|
||||||
|
|
||||||
Umi.UI.Markup = (function() {
|
|
||||||
const insertRaw = function(start, end) {
|
|
||||||
const selectionLength = Umi.UI.View.GetSelectionLength();
|
|
||||||
Umi.UI.View.EnterAtCursor(start);
|
|
||||||
Umi.UI.View.SetPosition(Umi.UI.View.GetPosition() + selectionLength + start.length);
|
|
||||||
Umi.UI.View.EnterAtCursor(end);
|
|
||||||
Umi.UI.View.SetPosition(Umi.UI.View.GetPosition() - selectionLength);
|
|
||||||
Umi.UI.View.SetPosition(Umi.UI.View.GetPosition() + selectionLength, true);
|
|
||||||
Umi.UI.View.Focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
let pickerTarget = document.body;
|
|
||||||
let picker, pickerVisible = false;
|
|
||||||
|
|
||||||
const insert = function(ev) {
|
|
||||||
if(this.dataset.umiTagName === 'color') {
|
|
||||||
if(picker === undefined) {
|
|
||||||
picker = new MamiColourPicker({ presets: futami.get('colours') });
|
|
||||||
pickerTarget.appendChild(picker.element);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pickerVisible) {
|
|
||||||
picker.close();
|
|
||||||
} else {
|
|
||||||
pickerVisible = true;
|
|
||||||
picker.dialog(ev)
|
|
||||||
.then(colour => insertRaw(`[color=${MamiColour.hex(colour)}]`, '[/color]'))
|
|
||||||
.catch(() => {}) // noop so the console stops screaming
|
|
||||||
.finally(() => pickerVisible = false);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
insertRaw(
|
|
||||||
this.dataset.umiBeforeCursor,
|
|
||||||
this.dataset.umiAfterCursor
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
SetPickerTarget: target => { pickerTarget = target; },
|
|
||||||
Add: function(name, text, beforeCursor, afterCursor) {
|
|
||||||
Umi.UI.InputMenus.Get('markup').appendChild($e({
|
|
||||||
tag: 'button',
|
|
||||||
attrs: {
|
|
||||||
type: 'button',
|
|
||||||
id: 'umi-msg-menu-markup-btn-' + name,
|
|
||||||
classList: ['markup__button', 'markup__button--' + name],
|
|
||||||
dataset: {
|
|
||||||
umiTagName: name,
|
|
||||||
umiBeforeCursor: beforeCursor,
|
|
||||||
umiAfterCursor: afterCursor,
|
|
||||||
},
|
|
||||||
onclick: insert,
|
|
||||||
},
|
|
||||||
child: text,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
Reset: function() {
|
|
||||||
Umi.UI.InputMenus.Get('markup').innerHTML = '';
|
|
||||||
},
|
|
||||||
Insert: insert,
|
|
||||||
InsertRaw: insertRaw,
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -375,9 +375,19 @@ Umi.UI.Messages = (function() {
|
||||||
const msgsList = $i('umi-messages');
|
const msgsList = $i('umi-messages');
|
||||||
return msgsList.scrollTop === (msgsList.scrollHeight - msgsList.offsetHeight);
|
return msgsList.scrollTop === (msgsList.scrollHeight - msgsList.offsetHeight);
|
||||||
},
|
},
|
||||||
ScrollIfNeeded: () => {
|
ScrollIfNeeded: (offsetOrForce = 0) => {
|
||||||
|
const msgsList = $i('umi-messages');
|
||||||
|
if(!(msgsList instanceof Element))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(typeof offsetOrForce === 'boolean' && offsetOrForce !== true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(typeof offsetOrForce === 'number' && msgsList.scrollTop < (msgsList.scrollHeight - msgsList.offsetHeight - offsetOrForce))
|
||||||
|
return;
|
||||||
|
|
||||||
if(mami.settings.get('autoScroll'))
|
if(mami.settings.get('autoScroll'))
|
||||||
$i('umi-messages').lastElementChild?.scrollIntoView({ inline: 'end' });
|
msgsList.lastElementChild?.scrollIntoView({ inline: 'end' });
|
||||||
},
|
},
|
||||||
SwitchChannel: channel => {
|
SwitchChannel: channel => {
|
||||||
if(typeof channel === 'object' && channel !== null && 'name' in channel)
|
if(typeof channel === 'object' && channel !== null && 'name' in channel)
|
||||||
|
@ -385,15 +395,13 @@ Umi.UI.Messages = (function() {
|
||||||
if(typeof channel !== 'string')
|
if(typeof channel !== 'string')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const isScrolledToBottom = Umi.UI.Messages.IsScrolledToBottom();
|
|
||||||
focusChannelName = channel;
|
focusChannelName = channel;
|
||||||
|
|
||||||
const root = $i('umi-messages');
|
const root = $i('umi-messages');
|
||||||
for(const elem of root.children)
|
for(const elem of root.children)
|
||||||
elem.classList.toggle('hidden', elem.dataset.channel !== undefined && elem.dataset.channel !== focusChannelName);
|
elem.classList.toggle('hidden', elem.dataset.channel !== undefined && elem.dataset.channel !== focusChannelName);
|
||||||
|
|
||||||
if(isScrolledToBottom)
|
Umi.UI.Messages.ScrollIfNeeded();
|
||||||
Umi.UI.Messages.ScrollIfNeeded();
|
|
||||||
},
|
},
|
||||||
Clear: retain => {
|
Clear: retain => {
|
||||||
if(typeof retain === 'string' && !isNaN(retain))
|
if(typeof retain === 'string' && !isNaN(retain))
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
#include utility.js
|
|
||||||
|
|
||||||
Umi.UI.View = (function() {
|
|
||||||
const getPosition = function(end) {
|
|
||||||
return $i('umi-msg-text')[end ? 'selectionEnd' : 'selectionStart'];
|
|
||||||
};
|
|
||||||
const setPosition = function(pos, end) {
|
|
||||||
$i('umi-msg-text')[end ? 'selectionEnd' : 'selectionStart'] = pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getText = function() {
|
|
||||||
return $i('umi-msg-text').value;
|
|
||||||
};
|
|
||||||
const setText = function(text) {
|
|
||||||
$i('umi-msg-text').value = text;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
Focus: function() {
|
|
||||||
$i('umi-msg-text').focus();
|
|
||||||
},
|
|
||||||
SetPosition: setPosition,
|
|
||||||
GetPosition: getPosition,
|
|
||||||
GetSelectionLength: function() {
|
|
||||||
let length = getPosition(true) - getPosition();
|
|
||||||
if(length < 0)
|
|
||||||
length = getPosition() - getPosition(true);
|
|
||||||
return length;
|
|
||||||
},
|
|
||||||
EnterAtCursor: function(text, overwrite) {
|
|
||||||
const value = getText();
|
|
||||||
const current = getPosition();
|
|
||||||
let out = '';
|
|
||||||
|
|
||||||
out += value.slice(0, current);
|
|
||||||
out += text;
|
|
||||||
out += value.slice(current + (overwrite ? text.length : 0));
|
|
||||||
|
|
||||||
setText(out);
|
|
||||||
setPosition(current);
|
|
||||||
},
|
|
||||||
GetText: getText,
|
|
||||||
SetText: setText,
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -166,6 +166,10 @@ const SockChatProtocol = function(dispatch, options) {
|
||||||
if(!ctx.isAuthed)
|
if(!ctx.isAuthed)
|
||||||
throw 'must be authenticated';
|
throw 'must be authenticated';
|
||||||
|
|
||||||
|
text = text.replace(/\t/g, ' ');
|
||||||
|
if(text.length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
// there's actually a pretty big bug here lol
|
// there's actually a pretty big bug here lol
|
||||||
// any unsupported command is gonna fall through to the actual channel you're in
|
// any unsupported command is gonna fall through to the actual channel you're in
|
||||||
if(!text.startsWith('/') && ctx.pseudoChannelName !== undefined)
|
if(!text.startsWith('/') && ctx.pseudoChannelName !== undefined)
|
||||||
|
|
Loading…
Reference in a new issue