Imported core stuff from Misuzu.

This commit is contained in:
flash 2025-02-20 20:49:09 +00:00
parent 5c71b34c24
commit 9841468f59
35 changed files with 314 additions and 399 deletions

View file

@ -14,7 +14,8 @@ const exec = require('util').promisify(require('child_process').exec);
debug: isDebug,
swc: {
es: 'es2021',
jsx: '$er',
jsx: '$element',
jsxf: '$fragment',
},
housekeep: [
pathJoin(__dirname, 'public', 'assets'),

27
src/mami.js/array.js Normal file
View file

@ -0,0 +1,27 @@
const $arrayRemoveAt = function(array, index) {
array.splice(index, 1);
};
const $arrayRemoveValue = function(array, item) {
let index;
while(array.length > 0 && (index = array.indexOf(item)) >= 0)
$arrayRemoveAt(array, index);
};
const $arrayRemoveAny = function(array, predicate) {
let index;
while(array.length > 0 && (index = array.findIndex(predicate)) >= 0)
$arrayRemoveAt(array, index);
};
const $arrayShuffle = function(array) {
if(array.length < 2)
return;
for(let i = array.length - 1; i > 0; --i) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
};

View file

@ -1,11 +1,10 @@
#include srle.js
#include utility.js
const MamiDetectAutoPlaySource = '/+NIxA!!FhpbmcA#PA$wA@fgAV$#q$#/$#A#OUxBTUUzLjEwMAIeA!)UCCQC8CIA@gA@H49wpKWgA!%&/+MYxA$NIA$ExBTUUzLjEwMFV^%/+MYxDsA@NIA$FV&&/+MYxHYA@NIA$FV&&';
const MamiDetectAutoPlay = async () => {
try {
await $e('audio', { src: 'data:audio/mpeg;base64,' + MamiSRLE.decode(MamiDetectAutoPlaySource) }).play();
await $element('audio', { src: 'data:audio/mpeg;base64,' + MamiSRLE.decode(MamiDetectAutoPlaySource) }).play();
} catch(ex) {
return false;
}

View file

@ -1,4 +1,3 @@
#include xhr.js
#include audio/source.js
const MamiAudioContext = function() {
@ -62,8 +61,8 @@ const MamiAudioContext = function() {
if(ctx === undefined)
return undefined;
const result = await $x.get(url, { type: 'arraybuffer' });
return await ctx.decodeAudioData(result.body);
const { body } = await $xhr.get(url, { type: 'arraybuffer' });
return await ctx.decodeAudioData(body);
},
createSource: (buffer, reverse) => {

View file

@ -1,6 +1,5 @@
#include args.js
#include colour.js
#include utility.js
#include controls/tabs.js
#include colpick/tgrid.jsx
#include colpick/tpresets.jsx
@ -101,8 +100,8 @@ const MamiColourPicker = function(options) {
},
onRemove: ctx => {
const name = ctx.info.name;
$rq(`.colpick-tab-${name}-button`);
$rq(`.colpick-tab-${name}-container`);
$query(`.colpick-tab-${name}-button`)?.remove();
$query(`.colpick-tab-${name}-container`)?.remove();
},
onSwitch: ctx => {
if(ctx.from !== undefined) {

View file

@ -1,5 +1,3 @@
#include xhr.js
const FutamiCommon = function(vars) {
vars = vars || {};
@ -15,14 +13,16 @@ const FutamiCommon = function(vars) {
if(noCache)
options.headers = { 'Cache-Control': 'no-cache' };
return (await $x.get(get(name), options)).body;
const { body } = await $xhr.get(get(name), options);
return body;
},
getApiJson: async (path, noCache) => {
const options = { type: 'json' };
if(noCache)
options.headers = { 'Cache-Control': 'no-cache' };
return (await $x.get(get('api') + path, options)).body;
const { body } = await $xhr.get(get('api') + path, options);
return body;
},
};
};
@ -31,7 +31,7 @@ FutamiCommon.load = async url => {
if(typeof url !== 'string' && 'FUTAMI_URL' in window)
url = window.FUTAMI_URL + '?t=' + Date.now().toString();
const { body } = await $x.get(url, {
const { body } = await $xhr.get(url, {
type: 'json',
headers: {
'Cache-Control': 'no-cache'

View file

@ -1,5 +1,4 @@
#include awaitable.js
#include utility.js
const MamiConnectionManager = function(client, settings, urls, eventTarget) {
const validateClient = value => {
@ -42,7 +41,7 @@ const MamiConnectionManager = function(client, settings, urls, eventTarget) {
url = undefined;
};
$as(urls);
$arrayShuffle(urls);
const attempt = () => {
started = Date.now();

View file

@ -1,6 +1,5 @@
#include animate.js
#include args.js
#include utility.js
const MamiMessageBoxContainer = function() {
const container = <div class="msgbox-container"/>;
@ -229,7 +228,7 @@ const MamiMessageBoxDialog = function(info) {
end: () => { dialog.style.opacity = '0'; },
});
$r(dialog);
dialog.remove();
},
cancel: () => {
doReject();

View file

@ -1,5 +1,4 @@
#include args.js
#include uniqstr.js
const MamiTabsControl = function(options) {
options = MamiArgs('options', options, define => {
@ -21,8 +20,6 @@ const MamiTabsControl = function(options) {
element = elementInfo;
} else if('element' in elementInfo) {
element = elementInfo.element;
} else if('getElement' in elementInfo) {
element = elementInfo.getElement();
} else throw 'elementInfo is not a valid type';
if(!(element instanceof Element))
@ -126,7 +123,7 @@ const MamiTabsControl = function(options) {
if(tabId !== undefined)
throw 'tabInfo has already been added';
tabId = MamiUniqueStr(8);
tabId = $rngs(8);
tabs.set(tabId, tabInfo);
if(title !== 'string' && 'title' in tabInfo)

View file

@ -1,5 +1,4 @@
#include args.js
#include utility.js
const MamiViewsControl = function(options) {
options = MamiArgs('options', options, define => {
@ -17,8 +16,6 @@ const MamiViewsControl = function(options) {
element = elementInfo;
} else if('element' in elementInfo) {
element = elementInfo.element;
} else if('getElement' in elementInfo) {
element = elementInfo.getElement();
} else throw 'elementInfo is not a valid type';
if(!(element instanceof Element))

View file

@ -1,5 +1,3 @@
#include xhr.js
const MamiEEPROM = function(endPoint, getAuthLine) {
if(typeof endPoint !== 'string')
throw 'endPoint must be a string';
@ -40,7 +38,7 @@ const MamiEEPROM = function(endPoint, getAuthLine) {
formData.append('src', appId);
formData.append('file', fileInput);
const { status, body } = await $x.post(`${endPoint}/uploads`, {
const { status, body } = await $xhr.post(`${endPoint}/uploads`, {
type: 'json',
headers: {
Authorization: getAuthLine(),
@ -77,7 +75,7 @@ const MamiEEPROM = function(endPoint, getAuthLine) {
if(typeof fileInfo.urlf !== 'string')
throw 'fileInfo.urlf must be a string';
const { status, body } = await $x.delete(fileInfo.urlf, {
const { status, body } = await $xhr.delete(fileInfo.urlf, {
type: 'json',
headers: {
Authorization: getAuthLine(),

View file

@ -1,6 +1,5 @@
#include concurrent.js
#include hash.js
#include xhr.js
const MamiEEPROMv2 = function(baseUrl, getAuthLine, poolName) {
if(typeof baseUrl !== 'string')
@ -37,7 +36,7 @@ const MamiEEPROMv2 = function(baseUrl, getAuthLine, poolName) {
params.append('name', name);
params.append('hash', await MamiHash(buffer));
const { status, body } = await $x.post(`${baseUrl}/v1/storage/pools/${poolName}/uploads`, {
const { status, body } = await $xhr.post(`${baseUrl}/v1/storage/pools/${poolName}/uploads`, {
type: 'json',
headers: {
Authorization: getAuthLine(),
@ -57,7 +56,7 @@ const MamiEEPROMv2 = function(baseUrl, getAuthLine, poolName) {
const offset = size * index;
buffer = buffer.subarray(offset, Math.min(buffer.length, offset + size));
const { status, body } = await $x.put(taskInfo.task_url, {
const { status, body } = await $xhr.put(taskInfo.task_url, {
type: 'json',
headers: {
'X-Content-Index': index,
@ -73,7 +72,7 @@ const MamiEEPROMv2 = function(baseUrl, getAuthLine, poolName) {
};
const finishTask = async (taskInfo) => {
const { status, body } = await $x.post(`${baseUrl}/v1/storage/tasks/${taskInfo.task_id}`, { type: 'json' });
const { status, body } = await $xhr.post(`${baseUrl}/v1/storage/tasks/${taskInfo.task_id}`, { type: 'json' });
if(body === null)
throw "Could not complete upload task for some reason.";
@ -146,7 +145,7 @@ const MamiEEPROMv2 = function(baseUrl, getAuthLine, poolName) {
if(typeof fileInfo.id !== 'string')
throw 'fileInfo.id must be a string';
const { status, body } = await $x.delete(`${baseUrl}/v1/storage/uploads/${fileInfo.id}`, {
const { status, body } = await $xhr.delete(`${baseUrl}/v1/storage/uploads/${fileInfo.id}`, {
type: 'json',
headers: {
Authorization: getAuthLine(),

View file

@ -1,5 +1,4 @@
#include args.js
#include utility.js
const MamiEmotePicker = function(args) {
args = MamiArgs('args', args, define => {
@ -35,7 +34,7 @@ const MamiEmotePicker = function(args) {
</div>;
const buildList = () => {
$rc(listElem);
$removeChildren(listElem);
for(const emote of emotes)
listElem.appendChild(<button class="emopick-emote" type="button" title={`:${emote.strings[0]}:`} data-strings={emote.strings.join(' ')} onclick={() => {

139
src/mami.js/html.js Normal file
View file

@ -0,0 +1,139 @@
const $id = document.getElementById.bind(document);
const $query = document.querySelector.bind(document);
const $queryAll = document.querySelectorAll.bind(document);
const $text = document.createTextNode.bind(document);
const $insertBefore = function(target, element) {
target.parentNode.insertBefore(element, target);
};
const $appendChild = function(element, child) {
switch(typeof child) {
case 'undefined':
break;
case 'string':
element.appendChild($text(child));
break;
case 'function':
$appendChild(element, child());
break;
case 'object':
if(child === null)
break;
if(child instanceof Node)
element.appendChild(child);
else if(child?.element instanceof Node)
element.appendChild(child.element);
else if(typeof child?.toString === 'function')
element.appendChild($text(child.toString()));
break;
default:
element.appendChild($text(child.toString()));
break;
}
};
const $appendChildren = function(element, ...children) {
for(const child of children)
$appendChild(element, child);
};
const $removeChildren = function(element) {
while(element.lastChild)
element.removeChild(element.lastChild);
};
const $fragment = function(props, ...children) {
const fragment = document.createDocumentFragment();
$appendChildren(fragment, ...children);
return fragment;
};
const $element = function(type, props, ...children) {
if(typeof type === 'function')
return new type(props ?? {}, ...children);
const element = document.createElement(type ?? 'div');
if(props)
for(let key in props) {
const prop = props[key];
if(prop === undefined || prop === null)
continue;
switch(typeof prop) {
case 'function':
if(key.substring(0, 2) === 'on')
key = key.substring(2).toLowerCase();
element.addEventListener(key, prop);
break;
case 'object':
if(prop instanceof Array) {
if(key === 'class')
key = 'classList';
const attr = element[key];
let addFunc = null;
if(attr instanceof Array)
addFunc = attr.push.bind(attr);
else if(attr instanceof DOMTokenList)
addFunc = attr.add.bind(attr);
if(addFunc !== null) {
for(let j = 0; j < prop.length; ++j)
addFunc(prop[j]);
} else {
if(key === 'classList')
key = 'class';
element.setAttribute(key, prop.toString());
}
} else {
if(key === 'class' || key === 'className')
key = 'classList';
let setFunc = null;
if(element[key] instanceof DOMTokenList)
setFunc = (ak, av) => { if(av) element[key].add(ak); };
else if(element[key] instanceof CSSStyleDeclaration)
setFunc = (ak, av) => {
if(ak.includes('-'))
element[key].setProperty(ak, av);
else
element[key][ak] = av;
};
else
setFunc = (ak, av) => { element[key][ak] = av; };
for(const attrKey in prop) {
const attrValue = prop[attrKey];
if(attrValue)
setFunc(attrKey, attrValue);
}
}
break;
case 'boolean':
if(prop)
element.setAttribute(key, '');
break;
default:
if(key === 'className')
key = 'class';
element.setAttribute(key, prop.toString());
break;
}
}
$appendChildren(element, ...children);
return element;
};

View file

@ -1,5 +1,10 @@
window.Umi = { UI: {} };
#include array.js
#include html.js
#include uniqstr.js
#include xhr.js
#include animate.js
#include args.js
#include awaitable.js
@ -14,9 +19,7 @@ window.Umi = { UI: {} };
#include parsing.js
#include themes.js
#include txtrigs.js
#include uniqstr.js
#include users.js
#include utility.js
#include weeb.js
#include audio/autoplay.js
#include chatform/form.jsx
@ -262,13 +265,9 @@ const MamiInit = async args => {
text: title,
createdButton: button => {
button.element.id = `umi-menu-icons-${baseId}`;
button.element.append($e({
attrs: { className: `sidebar__selector-mode--${baseId}` },
}));
button.element.append($element('div', { className: `sidebar__selector-mode--${baseId}` }));
},
element: $e({
attrs: { 'class': `sidebar__menu--${baseId}`, id: `umi-menus-${baseId}` }
}),
element: $element('div', { 'class': `sidebar__menu--${baseId}`, id: `umi-menus-${baseId}` }),
});
}
});
@ -313,7 +312,7 @@ const MamiInit = async args => {
if(bbCode.tag === 'color') {
if(colourPicker === undefined) {
colourPicker = new MamiColourPicker({ presets: futami.get('colours') });
layout.getElement().appendChild(colourPicker.element);
layout.element.appendChild(colourPicker.element);
}
if(colourPickerVisible) {
@ -339,19 +338,19 @@ const MamiInit = async args => {
});
settings.watch('style', ev => {
for(const className of layout.getElement().classList)
for(const className of layout.element.classList)
if(className.startsWith('umi--'))
layout.getElement().classList.remove(className);
layout.getElement().classList.add(`umi--${ev.detail.value}`);
layout.element.classList.remove(className);
layout.element.classList.add(`umi--${ev.detail.value}`);
UmiThemeApply(ev.detail.value);
});
settings.watch('compactView', ev => {
layout.getElement().classList.toggle('chat--compact', ev.detail.value);
layout.getInterface().getMessageList().getElement().classList.toggle('chat--compact', ev.detail.value);
layout.element.classList.toggle('chat--compact', ev.detail.value);
layout.interface.messageList.element.classList.toggle('chat--compact', 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.interface.messageList.element.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; });
@ -705,7 +704,7 @@ const MamiInit = async args => {
getEmotes: () => MamiEmotes.all(Umi.User.getCurrentUser().perms.rank),
setKeepOpenOnPick: value => { settings.set('keepEmotePickerOpen', value); },
});
layout.getElement().appendChild(emotePicker.element);
layout.element.appendChild(emotePicker.element);
settings.watch('keepEmotePickerOpen', ev => { emotePicker.keepOpenOnPick = ev.detail.value; });
}
@ -799,16 +798,13 @@ const MamiInit = async args => {
}
};
const uploadForm = $e({
tag: 'input',
attrs: {
type: 'file',
multiple: true,
style: { display: 'none' },
onchange: ev => {
for(const file of ev.target.files)
doUpload(file);
},
const uploadForm = $element('input', {
type: 'file',
multiple: true,
style: { display: 'none' },
onchange: ev => {
for(const file of ev.target.files)
doUpload(file);
},
});
args.parent.appendChild(uploadForm);
@ -981,18 +977,16 @@ const MamiDbgCreateFloatingInstance = async () => {
if(!FUTAMI_DEBUG)
return;
const prefix = MamiUniqueStr(8);
const parent = $e({
attrs: {
style: {
position: 'absolute',
bottom: '100px',
right: '100px',
zIndex: '9001',
width: '640px',
height: '480px',
background: '#0f0',
},
const prefix = $rngs(8);
const parent = $element('div', {
style: {
position: 'absolute',
bottom: '100px',
right: '100px',
zIndex: '9001',
width: '640px',
height: '480px',
background: '#0f0',
},
});

View file

@ -1,5 +1,4 @@
#include common.js
#include xhr.js
const MamiMisuzuAuth = (() => {
let userId = null;
@ -18,7 +17,7 @@ const MamiMisuzuAuth = (() => {
};
},
update: async () => {
const { body } = await $x.get(futami.get('token'), { authed: true, type: 'json' });
const { body } = await $xhr.get(futami.get('token'), { authed: true, type: 'json' });
if(body.ok) {
userId = body.usr.toString();
authToken = body.tkn;

View file

@ -1,5 +1,3 @@
#include utility.js
if(!Umi.Parser) Umi.Parser = {};
if(!Umi.Parser.SockChatBBcode) Umi.Parser.SockChatBBcode = {};
@ -146,8 +144,9 @@ Umi.Parsing = (function() {
};
};
const motivFrame = function(texts, body) {
return $e({
attrs: {
return $element(
'div',
{
style: {
display: 'inline-block',
textAlign: 'center',
@ -158,48 +157,46 @@ Umi.Parsing = (function() {
lineHeight: '1.4em',
},
},
child: [
$element(
'div',
{
tag: 'div',
attrs: {
style: {
border: '3px double #fff',
maxWidth: '50vw',
maxHeight: '50vh',
marginTop: '30px',
marginLeft: '50px',
marginRight: '50px',
boxSizing: 'content-box',
display: 'inline-block',
},
},
child: body,
},
{
tag: 'h1',
child: texts.top,
attrs: {
style: {
color: '#fff',
textDecoration: 'none !important',
margin: '10px',
textTransform: 'uppercase',
},
style: {
border: '3px double #fff',
maxWidth: '50vw',
maxHeight: '50vh',
marginTop: '30px',
marginLeft: '50px',
marginRight: '50px',
boxSizing: 'content-box',
display: 'inline-block',
},
},
body
),
$element(
'h1',
{
tag: 'p',
child: texts.bottom,
attrs: {
style: {
color: '#fff',
textDecoration: 'none !important',
margin: '10px',
},
style: {
color: '#fff',
textDecoration: 'none !important',
margin: '10px',
textTransform: 'uppercase',
},
},
],
});
texts.top
),
$element(
'p',
{
style: {
color: '#fff',
textDecoration: 'none !important',
margin: '10px',
},
},
texts.bottom
),
);
};
const toggleImage = function(element) {
@ -219,9 +216,9 @@ Umi.Parsing = (function() {
element.dataset.embed = '1';
element.classList.add('markup__link--visited');
let html = $e({
tag: 'img',
attrs: {
let html = $element(
'img',
{
src: url,
alt: url,
style: {
@ -234,7 +231,7 @@ Umi.Parsing = (function() {
container.scrollIntoView({ inline: 'end' });
},
},
});
);
if(mami.settings.get('motivationalImages'))
html = motivFrame(
@ -256,16 +253,16 @@ Umi.Parsing = (function() {
element.dataset.embed = '0';
element.textContent = 'Embed';
container.textContent = '';
container.appendChild($e({
tag: 'a',
attrs: {
container.appendChild($element(
'a',
{
href: url,
target: '_blank',
rel: 'nofollow noreferrer noopener',
className: 'markup__link',
},
child: url,
}));
url,
));
} else {
container.title = 'audio';
element.dataset.embed = '1';
@ -273,9 +270,9 @@ Umi.Parsing = (function() {
container.textContent = '';
element.classList.add('markup__link--visited');
let media = $e({
tag: 'audio',
attrs: {
let media = $element(
'audio',
{
src: url,
controls: true,
onloadedmetadata: () => {
@ -285,7 +282,7 @@ Umi.Parsing = (function() {
media.play();
},
},
});
);
container.appendChild(media);
}
@ -300,25 +297,25 @@ Umi.Parsing = (function() {
element.dataset.embed = '0';
element.textContent = 'Embed';
container.textContent = '';
container.appendChild($e({
tag: 'a',
attrs: {
container.appendChild($element(
'a',
{
href: url,
target: '_blank',
rel: 'nofollow noreferrer noopener',
className: 'markup__link',
},
child: url,
}));
url,
));
} else {
container.title = 'video';
element.dataset.embed = '1';
element.textContent = 'Remove';
element.classList.add('markup__link--visited');
let media = $e({
tag: 'video',
attrs: {
let media = $element(
'video',
{
src: url,
controls: true,
style: {
@ -332,7 +329,7 @@ Umi.Parsing = (function() {
media.play();
},
},
});
);
let html = media;

View file

@ -1,5 +1,3 @@
#include utility.js
const MamiSettingsBackup = function(settings) {
const header = 'Mami Settings Export';
const version = 1;
@ -88,7 +86,7 @@ const MamiSettingsBackup = function(settings) {
fileName = 'settings.mami';
const data = exportData();
const html = $e('a', {
const html = $element('a', {
href: URL.createObjectURL(new Blob([data], { type: 'application/octet-stream' })),
download: fileName,
target: '_blank',
@ -102,7 +100,7 @@ const MamiSettingsBackup = function(settings) {
importFile: importFile,
importUpload: (target) => {
return new Promise((resolve, reject) => {
const html = $e('input', {
const html = $element('input', {
type: 'file',
accept: '.mami',
style: { display: 'none' },

View file

@ -1,5 +1,3 @@
#include utility.js
const MamiSidebarActionCollapseAll = function() {
// this should take a reference to the message container rather than just using $qa
@ -12,7 +10,7 @@ const MamiSidebarActionCollapseAll = function() {
},
onclick: () => {
const buttons = $qa('[data-embed="1"]');
const buttons = $queryAll('[data-embed="1"]');
for(const button of buttons)
button.click();
},

View file

@ -191,7 +191,7 @@ const MamiSidebarPanelSettings = function(settings, msgBox) {
const updateSelectOptions = () => {
const options = setting.options();
$rc(input);
$removeChildren(input);
for(const name in options)
input.appendChild(<option class={`setting__style setting__style--${setting.name}-${name}`} value={name}>
{options[name]}

View file

@ -1,5 +1,3 @@
#include utility.js
const MamiSidebarPanelUploadsEntry = function(fileInfo) {
const options = new Map;
let uploadInfo;
@ -164,7 +162,7 @@ const MamiSidebarPanelUploads = function() {
return;
html.removeChild(entry.element);
$ari(entries, entry);
$arrayRemoveValue(entries, entry);
},
addOption: option => {

View file

@ -1,6 +1,5 @@
#include animate.js
#include users.js
#include utility.js
const MamiSidebarPanelUsersEntry = function(info) {
const id = info.id;

View file

@ -1,5 +1,3 @@
#include utility.js
const MamiSoundManager = function(context) {
const probablySupported = [];
const maybeSupported = [];
@ -12,7 +10,7 @@ const MamiSoundManager = function(context) {
};
(() => {
const elem = $e('audio');
const elem = $element('audio');
for(const name in formats) {
const format = formats[name];
const support = elem.canPlayType(format);

View file

@ -130,7 +130,7 @@ const MamiSoundTest = function(settings, audio, manager, library, clickPos) {
console.error(ex);
state.textContent = `Error: ${ex}`;
} finally {
$ari(sources, source);
$arrayRemoveValue(sources, source);
await MamiSleep(2000);
nowPlaying.removeChild(player);
}

View file

@ -311,7 +311,7 @@ const UmiThemeApply = function(theme) {
if(theme.colours) {
if(typeof theme.colours['main-background'] === 'number') {
const themeColour = $q('meta[name="theme-color"]');
const themeColour = $query('meta[name="theme-color"]');
if(themeColour instanceof Element)
themeColour.content = MamiColour.hex(theme.colours['main-background']);
}

View file

@ -1,25 +1,12 @@
#include utility.js
#include ui/chat-message-list.js
Umi.UI.ChatInterface = function(chatForm) {
const messages = new Umi.UI.ChatMessageList;
const html = $e({
attrs: {
className: 'main',
},
child: [
messages,
chatForm,
],
});
const element = $element('div', { className: 'main' }, messages, chatForm);
return {
getMessageList: function() {
return messages;
},
getElement: function() {
return html;
},
get messageList() { return messages; },
get element() { return element; },
};
};

View file

@ -1,27 +1,12 @@
#include utility.js
#include ui/chat-interface.js
// this needs revising at some point but will suffice for now
Umi.UI.ChatLayout = function(chatForm, sideBar) {
const main = new Umi.UI.ChatInterface(chatForm);
const html = $e({
attrs: {
id: 'umi-chat',
className: 'umi',
},
child: [
main,
sideBar,
],
});
const element = $element('div', { id: 'umi-chat', className: 'umi' }, main, sideBar);
return {
getInterface: function() {
return main;
},
getElement: function() {
return html;
},
get interface() { return main; },
get element() { return element; },
};
};

View file

@ -1,16 +1,7 @@
#include utility.js
Umi.UI.ChatMessageList = function() {
const html = $e({
attrs: {
id: 'umi-messages',
className: 'chat',
},
});
const element = $element('div', { id: 'umi-messages', className: 'chat' });
return {
getElement: function() {
return html;
},
get element() { return element; },
};
};

View file

@ -1,5 +1,4 @@
#include emotes.js
#include utility.js
Umi.UI.Emoticons = (function() {
return {
@ -7,13 +6,7 @@ Umi.UI.Emoticons = (function() {
let inner = element.innerHTML;
MamiEmotes.forEach(author?.perms?.rank ?? 0, function(emote) {
const image = $e({
tag: 'img',
attrs: {
className: 'emoticon',
src: emote.url,
},
});
const image = $element('img', { className: 'emoticon', src: emote.url });
for (const i in emote.strings) {
const trigger = ':' + emote.strings[i] + ':',

View file

@ -1,5 +1,3 @@
#include utility.js
Umi.UI.LoadingOverlay = function(icon, header, message) {
const icons = {
'spinner': 'fas fa-3x fa-fw fa-spinner fa-pulse',

View file

@ -4,7 +4,6 @@
#include txtrigs.js
#include url.js
#include users.js
#include utility.js
#include weeb.js
#include sound/umisound.js
#include ui/emotes.js
@ -97,7 +96,7 @@ Umi.UI.Messages = (function() {
Add: function(msg) {
const elementId = `message-${msg.id}`;
if(msg.id !== '' && $i(elementId))
if(msg.id !== '' && $id(elementId))
return;
let isTiny = false;
@ -254,8 +253,8 @@ Umi.UI.Messages = (function() {
}
if(mami.settings.get('weeaboo')) {
eUser.appendChild($t(Weeaboo.getNameSuffix(msgAuthor)));
eText.appendChild($t(Weeaboo.getTextSuffix(msgAuthor)));
eUser.appendChild($text(Weeaboo.getNameSuffix(msgAuthor)));
eText.appendChild($text(Weeaboo.getTextSuffix(msgAuthor)));
const kaomoji = Weeaboo.getRandomKaomoji(true, msg);
if(kaomoji)
@ -269,7 +268,7 @@ Umi.UI.Messages = (function() {
else
eAvatar.classList.add('message__avatar--disabled');
const msgsList = $i('umi-messages');
const msgsList = $id('umi-messages');
let insertAfter = msgsList.lastElementChild;
if(insertAfter instanceof Element) {
@ -372,11 +371,11 @@ Umi.UI.Messages = (function() {
mami.globalEvents.dispatch('umi:ui:message_add', { element: eBase });
},
IsScrolledToBottom: () => {
const msgsList = $i('umi-messages');
const msgsList = $id('umi-messages');
return msgsList.scrollTop === (msgsList.scrollHeight - msgsList.offsetHeight);
},
ScrollIfNeeded: (offsetOrForce = 0) => {
const msgsList = $i('umi-messages');
const msgsList = $id('umi-messages');
if(!(msgsList instanceof Element))
return;
@ -397,7 +396,7 @@ Umi.UI.Messages = (function() {
focusChannelName = channel;
const root = $i('umi-messages');
const root = $id('umi-messages');
for(const elem of root.children)
elem.classList.toggle('hidden', elem.dataset.channel !== undefined && elem.dataset.channel !== focusChannelName);
@ -409,7 +408,7 @@ Umi.UI.Messages = (function() {
if(typeof retain !== 'number')
return;
const root = $i('umi-messages');
const root = $id('umi-messages');
// remove messages
if(root.childElementCount > retain)
@ -419,7 +418,7 @@ Umi.UI.Messages = (function() {
if(!elem.dataset.channel || elem.classList.contains('hidden') || --retain > 0)
continue;
$r(elem);
elem.remove();
}
// fix author display
@ -441,7 +440,7 @@ Umi.UI.Messages = (function() {
if(msgId === '')
return;
const elem = $i(`message-${msgId}`);
const elem = $id(`message-${msgId}`);
if(!(elem instanceof Element))
return;
@ -449,7 +448,7 @@ Umi.UI.Messages = (function() {
if(elem.nextElementSibling && elem.nextElementSibling.dataset.author === elem.dataset.author)
elem.nextElementSibling.classList.add('message--first');
$r(elem);
elem.remove();
},
};
})();

View file

@ -1,4 +1,4 @@
const MamiRandomInt = (min, max) => {
const $rngi = (min, max) => {
let ret = 0;
const range = max - min;
@ -21,18 +21,18 @@ const MamiRandomInt = (min, max) => {
ret &= mask;
if(ret >= range)
return MamiRandomInt(min, max);
return $rngi(min, max);
return min + ret;
};
const MamiUniqueStr = (() => {
const $rngs = (() => {
const chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789';
return length => {
let str = '';
for(let i = 0; i < length; ++i)
str += chars[MamiRandomInt(0, chars.length)];
str += chars[$rngi(0, chars.length)];
return str;
};
})();

View file

@ -1,170 +0,0 @@
const $i = document.getElementById.bind(document);
const $c = document.getElementsByClassName.bind(document);
const $q = document.querySelector.bind(document);
const $qa = document.querySelectorAll.bind(document);
const $t = document.createTextNode.bind(document);
const $r = function(element) {
if(element && element.parentNode)
element.parentNode.removeChild(element);
};
const $ri = function(name) {
$r($i(name));
};
const $rq = function(query) {
$r($q(query));
};
const $ib = function(ref, elem) {
ref.parentNode.insertBefore(elem, ref);
};
const $rc = function(element) {
while(element.lastChild)
element.removeChild(element.lastChild);
};
const $e = function(info, attrs, child, created) {
info = info || {};
if(typeof info === 'string') {
info = {tag: info};
if(attrs)
info.attrs = attrs;
if(child)
info.child = child;
if(created)
info.created = created;
}
const elem = document.createElement(info.tag || 'div');
if(info.attrs) {
const attrs = info.attrs;
for(let key in attrs) {
const attr = attrs[key];
if(attr === undefined || attr === null)
continue;
switch(typeof attr) {
case 'function':
if(key.substring(0, 2) === 'on')
key = key.substring(2).toLowerCase();
elem.addEventListener(key, attr);
break;
case 'object':
if(attr instanceof Array) {
if(key === 'class')
key = 'classList';
const prop = elem[key];
let addFunc = null;
if(prop instanceof Array)
addFunc = prop.push.bind(prop);
else if(prop instanceof DOMTokenList)
addFunc = prop.add.bind(prop);
if(addFunc !== null) {
for(let j = 0; j < attr.length; ++j)
addFunc(attr[j]);
} else {
if(key === 'classList')
key = 'class';
elem.setAttribute(key, attr.toString());
}
} else {
for(const attrKey in attr)
elem[key][attrKey] = attr[attrKey];
}
break;
case 'boolean':
if(attr)
elem.setAttribute(key, '');
break;
default:
if(key === 'className')
key = 'class';
elem.setAttribute(key, attr.toString());
break;
}
}
}
if(info.child) {
let children = info.child;
if(!Array.isArray(children))
children = [children];
for(const child of children) {
switch(typeof child) {
case 'string':
elem.appendChild($t(child));
break;
case 'object':
if(child instanceof Element) {
elem.appendChild(child);
} else if('element' in child) {
const childElem = child.element;
if(childElem instanceof Element)
elem.appendChild(childElem);
else
elem.appendChild($e(child));
} else if('getElement' in child) {
const childElem = child.getElement();
if(childElem instanceof Element)
elem.appendChild(childElem);
else
elem.appendChild($e(child));
} else {
elem.appendChild($e(child));
}
break;
default:
elem.appendChild($t(child.toString()));
break;
}
}
}
if(info.created)
info.created(elem);
return elem;
};
const $er = (type, props, ...children) => $e({ tag: type, attrs: props, child: children });
const $ar = function(array, index) {
array.splice(index, 1);
};
const $ari = function(array, item) {
let index;
while(array.length > 0 && (index = array.indexOf(item)) >= 0)
$ar(array, index);
};
const $arf = function(array, predicate) {
let index;
while(array.length > 0 && (index = array.findIndex(predicate)) >= 0)
$ar(array, index);
};
const $as = function(array) {
if(array.length < 2)
return;
for(let i = array.length - 1; i > 0; --i) {
let j = Math.floor(Math.random() * (i + 1)),
tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
};

View file

@ -1,6 +1,5 @@
#include common.js
#include rng.js
#include xhr.js
const Weeaboo = (function() {
let kaomoji = [];
@ -16,7 +15,7 @@ const Weeaboo = (function() {
if(kaomoji.length > 0)
return;
$x.get(futami.get('kaomoji'))
$xhr.get(futami.get('kaomoji'))
.then(resp => kaomoji = resp.text.split("\n"));
};

View file

@ -1,4 +1,4 @@
const $x = (function() {
const $xhr = (function() {
const send = function(method, url, options, body) {
if(options === undefined)
options = {};