Imported Ami into own repository.

This commit is contained in:
flash 2024-01-18 19:51:52 +00:00
commit af337ab2cf
88 changed files with 10827 additions and 0 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
[Tt]humbs.db
[Dd]esktop.ini
.DS_Store
/config/config.json
/public/robots.txt
/.debug
/node_modules
/public/assets
/public/index.html

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# Ami
Chat client forked from the original Sock Chat client. Maintained for compatibility with Firefox 10+ and similar browsers.

Binary file not shown.

208
build.js Normal file
View file

@ -0,0 +1,208 @@
// IMPORTS
const fs = require('fs');
const swc = require('@swc/core');
const path = require('path');
const util = require('util');
const postcss = require('postcss');
const jsminify = require('terser').minify;
const htmlminify = require('html-minifier-terser').minify;
const utils = require('./src/utils.js');
const assproc = require('./src/assproc.js');
// CONFIG
const rootDir = __dirname;
const configFile = path.join(rootDir, 'config/config.json');
const srcDir = path.join(rootDir, 'src');
const pubDir = path.join(rootDir, 'public');
const pubAssetsDir = path.join(pubDir, 'assets');
const isDebugBuild = fs.existsSync(path.join(rootDir, '.debug'));
const buildTasks = {
js: [
{ source: 'ami.js', target: '/assets', name: 'ami.{hash}.js', },
],
css: [
{ source: 'ami.css', target: '/assets', name: 'ami.{hash}.css', },
],
html: [
{ source: 'ami.html', target: '/', name: 'index.html', },
],
};
// PREP
const config = JSON.parse(fs.readFileSync(configFile));
const postcssPlugins = [];
if(!isDebugBuild) postcssPlugins.push(require('cssnano'));
postcssPlugins.push(require('autoprefixer')({
remove: false,
}));
const swcJscOptions = {
target: 'es5',
loose: false,
externalHelpers: false,
keepClassNames: true,
preserveAllComments: false,
transform: {},
parser: {
syntax: 'ecmascript',
jsx: true,
dynamicImport: false,
privateMethod: false,
functionBind: false,
exportDefaultFrom: false,
exportNamespaceFrom: false,
decorators: false,
decoratorsBeforeExport: false,
topLevelAwait: true,
importMeta: false,
},
transform: {
react: {
runtime: 'classic',
pragma: '$er',
},
},
};
const htmlMinifyOptions = {
collapseBooleanAttributes: true,
collapseWhitespace: true,
conservativeCollapse: false,
decodeEntities: false,
quoteCharacter: '"',
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: true,
removeOptionalTags: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
sortAttributes: true,
sortClassName: true,
};
// BUILD
(async () => {
const htmlVars = { 'title': config.title };
const buildVars = {
FUTAMI_DEBUG: isDebugBuild,
FUTAMI_URL: config.common_url,
MAMI_URL: config.modern_url,
AMI_URL: config.compat_url,
};
console.log('Ensuring assets directory exists...');
fs.mkdirSync(pubAssetsDir, { recursive: true });
console.log();
console.log('JS assets');
for(const info of buildTasks.js) {
console.log(`=> Building ${info.source}...`);
let origTarget = undefined;
if('es' in info) {
origTarget = swcJscOptions.target;
swcJscOptions.target = info.es;
}
const assprocOpts = {
prefix: '#',
entry: info.entry || 'main.js',
buildVars: buildVars,
};
const swcOpts = {
filename: info.source,
sourceMaps: false,
isModule: false,
minify: !isDebugBuild,
jsc: swcJscOptions,
};
const pubName = await assproc.process(path.join(srcDir, info.source), assprocOpts)
.then(output => swc.transform(output, swcOpts))
.then(output => {
const name = utils.strtr(info.name, { hash: utils.shortHash(output.code) });
const pubName = path.join(info.target || '', name);
console.log(` Saving to ${pubName}...`);
fs.writeFileSync(path.join(pubDir, pubName), output.code);
return pubName;
});
if(origTarget !== undefined)
swcJscOptions.target = origTarget;
htmlVars[info.source] = pubName;
if(typeof info.buildVar === 'string')
buildVars[info.buildVar] = pubName;
}
console.log();
console.log('CSS assets');
for(const info of buildTasks.css) {
console.log(`=> Building ${info.source}...`);
const sourcePath = path.join(srcDir, info.source);
const assprocOpts = {
prefix: '@',
entry: info.entry || 'main.css',
};
const postcssOpts = { from: sourcePath };
htmlVars[info.source] = await assproc.process(sourcePath, assprocOpts)
.then(output => postcss(postcssPlugins).process(output, postcssOpts)
.then(output => {
const name = utils.strtr(info.name, { hash: utils.shortHash(output.css) });
const pubName = path.join(info.target || '', name);
console.log(` Saving to ${pubName}...`);
fs.writeFileSync(path.join(pubDir, pubName), output.css);
return pubName;
}));
}
console.log();
console.log('HTML assets');
for(const info of buildTasks.html) {
console.log(`=> Building ${info.source}...`);
try {
let data = fs.readFileSync(path.join(srcDir, info.source));
data = utils.strtr(data, htmlVars);
if(!isDebugBuild)
data = await htmlminify(data, htmlMinifyOptions);
const name = utils.strtr(info.name, { hash: utils.shortHash(data) });
const pubName = path.join(info.target || '', name);
console.log(` Saving to ${pubName}...`);
const fullPath = path.join(pubDir, pubName);
const dirPath = path.dirname(fullPath);
if(!fs.existsSync(dirPath))
fs.mkdirSync(dirPath, { recursive: true });
fs.writeFileSync(fullPath, data);
htmlVars[info.source] = pubName;
} catch(err) {
console.error(err);
}
}
console.log();
console.log('Cleaning up old builds...');
assproc.housekeep(pubAssetsDir);
})();

View file

@ -0,0 +1,6 @@
{
"title": "Flashii Chat",
"common_url": "//futami.flashii.net/common.json",
"modern_url": "//chat.flashii.net",
"compat_url": "//sockchat.flashii.net"
}

1372
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

10
package.json Normal file
View file

@ -0,0 +1,10 @@
{
"dependencies": {
"@swc/core": "^1.3.104",
"autoprefixer": "^10.4.17",
"cssnano": "^6.0.3",
"html-minifier-terser": "^7.2.0",
"postcss": "^8.4.33",
"terser": "^5.27.0"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
public/images/alert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
public/images/load.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
public/images/sprite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

261
public/picker.css Normal file
View file

@ -0,0 +1,261 @@
.fw-colour-picker {
border-radius: 5px;
border: 2px solid #123456;
padding: 3px;
display: flex;
position: absolute;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
background-color: #444;
color: #fff;
box-shadow: 0 3px 10px #000;
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
font-size: 12px;
line-height: 20px;
width: 290px;
transition: width .2s, border-color .2s;
}
.fw-colour-picker-form {
width: 100%;
}
.fw-colour-picker-tabbed {
width: 100%;
margin-bottom: 3px;
}
.fw-colour-picker-tabbed-container {
border: 2px solid #123456;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
border-radius: 5px 5px 0 0;
height: 234px;
overflow: auto;
transition: border-color .2s;
}
.fw-colour-picker-tabbed-list {
background-color: #222;
border-radius: 0 0 5px 5px;
overflow: auto;
}
.fw-colour-picker-tab-button {
background: #333;
color: #fff;
border-radius: 0;
border-width: 0;
display: inline-block;
padding: 3px 5px;
height: 24px;
margin-right: 1px;
transition: background .2s, border-color .2s;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
cursor: pointer;
}
.fw-colour-picker-tab-button:hover {
background: #444;
border-color: #444;
}
.fw-colour-picker-tab-button:focus {
border-color: #fff;
}
.fw-colour-picker-tab-button-active {
background: #123456;
border-color: #123456;
color: #123456;
}
.fw-colour-picker-middle-row {
width: 100%;
margin-bottom: 3px;
height: 60px;
}
.fw-colour-picker-colour-preview-container {
display: inline-block;
width: 60px;
height: 60px;
vertical-align: middle;
}
.fw-colour-picker-colour-preview {
display: block;
width: 60px;
height: 60px;
border-radius: 5px;
background: #123456;
}
.fw-colour-picker-values-container {
display: inline-block;
vertical-align: middle;
}
.fw-colour-picker-values-child {
display: block;
padding: 2px 3px;
margin: 1px;
margin-left: 31px;
cursor: text;
}
.fw-colour-picker-values-child-label {
font-weight: 700;
width: 40px;
display: inline-block;
margin-right: 6px;
text-align: right;
}
.fw-colour-picker-values-child-input {
display: inline-block;
border: 1px solid #222;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
background: #333;
border-radius: 0;
color: #fff;
padding: 1px;
width: 100px;
transition: border-color .2s;
outline-style: none;
}
.fw-colour-picker-values-child-input:focus {
border-color: #777;
}
.fw-colour-picker-buttons-container {
text-align: right;
margin: 1px;
}
.fw-colour-picker-buttons-container-inner {
text-align: left;
display: inline-block;
}
.fw-colour-picker-buttons-button {
border-radius: 5px;
background: #222;
border-width: 0;
color: #fff;
font-size: 1.1em;
line-height: 1.2em;
padding: 3px 10px;
margin-left: 5px;
cursor: pointer;
}
.fw-colour-picker-buttons-button:focus {
box-shadow: 0 0 0 1px #000, inset 0 0 0 1px #fff;
}
.fw-colour-picker-buttons-button-submit {
font-weight: 700;
color: #123456;
background: #123456;
transition: background .2s;
}
.fw-colour-picker-tab-container {
display: none;
}
.fw-colour-picker-tab-container-active {
display: block;
}
.fw-colour-picker-tab-presets-container {
text-align: center;
padding-top: 3px;
padding-left: 3px;
}
.fw-colour-picker-presets-option {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 40px;
margin-right: 3px;
margin-bottom: 3px;
border-radius: 5px;
text-decoration: none;
color: #fff;
}
.fw-colour-picker-presets-option:focus {
box-shadow: 0 0 0 1px #fff, 0 0 0 2px #000, inset 0 0 0 1px #000;
}
.fw-colour-picker-presets-option-active {
box-shadow: 0 0 0 1px #000, inset 0 0 0 1px #fff;
}
.fw-colour-picker-tab-grid-container {
text-align: center;
}
.fw-colour-picker-grid-option {
display: inline-block;
vertical-align: middle;
width: 23px;
height: 23px;
z-index: 1;
position: relative;
cursor: cell;
}
.fw-colour-picker-grid-option:focus {
box-shadow: 0 0 0 1px #fff, 0 0 0 2px #000, inset 0 0 0 1px #000;
z-index: 3;
}
.fw-colour-picker-grid-option-active {
box-shadow: 0 0 0 1px #000, inset 0 0 0 1px #fff;
z-index: 2;
}
.fw-colour-picker-slider {
margin: 0 2px;
}
.fw-colour-picker-slider-gradient {
width: 100%;
height: 6px;
}
.fw-colour-picker-slider-name {
font-weight: 700;
margin: 0 2px;
}
.fw-colour-picker-slider-value-slider-container {
display: inline-block;
width: 200px;
margin: 4px;
}
.fw-colour-picker-slider-value-slider {
width: 100%;
margin: 0;
}
.fw-colour-picker-slider-value-input-container {
display: inline-block;
vertical-align: top;
text-align: center;
width: 64px;
}
.fw-colour-picker-slider-value-input {
display: inline-block;
border: 1px solid #222;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
background: #333;
border-radius: 0;
color: #fff;
padding: 1px;
width: 50px;
transition: border-color .2s;
outline-style: none;
}
.fw-colour-picker-slider-value-input:focus {
border-color: #777;
}
.fw-colour-picker-slider-red .fw-colour-picker-slider-value-input:focus {
border-color: #f00;
}
.fw-colour-picker-slider-green .fw-colour-picker-slider-value-input:focus {
border-color: #0f0;
}
.fw-colour-picker-slider-blue .fw-colour-picker-slider-value-input:focus {
border-color: #00f;
}

505
public/picker.js Normal file
View file

@ -0,0 +1,505 @@
var FwColourPicker = function(callback, options, colour, onClose) {
if(typeof callback !== 'function')
return;
if(typeof colour !== 'number')
colour = parseInt(colour || 0);
if(typeof options !== 'object')
options = {};
var readThres = 168,
lumiRed = .299,
lumiGreen = .587,
lumiBlue = .114;
var extractRGB = function(raw) {
return [
(raw >> 16) & 0xFF,
(raw >> 8) & 0xFF,
raw & 0xFF,
];
};
var calcLumi = function(raw) {
var rgb = extractRGB(raw);
return rgb[0] * lumiRed
+ rgb[1] * lumiGreen
+ rgb[2] * lumiBlue;
};
var textColour = function(raw) {
return calcLumi(raw) > readThres ? 0 : 0xFFFFFF;
};
var weightNum = function(n1, n2, w) {
w = Math.min(1, Math.max(0, w));
return Math.round((n1 * w) + (n2 * (1 - w)));
};
var weightColour = function(c1, c2, w) {
c1 = extractRGB(c1);
c2 = extractRGB(c2);
return (weightNum(c1[0], c2[0], w) << 16)
| (weightNum(c1[1], c2[1], w) << 8)
| weightNum(c1[2], c2[2], w);
};
var shadeColour = function(raw, offset) {
if(offset == 0)
return raw;
var dir = 0xFFFFFF;
if(offset < 0) {
dir = 0;
offset *= -1;
}
return weightColour(dir, raw, offset);
};
var hexFormat = FwColourPicker.hexFormat;
var verifyOption = function(name, type, def) {
if(typeof options[name] !== type)
options[name] = def;
};
verifyOption('posX', 'number', -1);
verifyOption('posY', 'number', -1);
verifyOption('presets', 'object', []);
verifyOption('showPresetsTab', 'boolean', options.presets.length > 0);
verifyOption('showGridTab', 'boolean', true);
verifyOption('showSlidersTab', 'boolean', true);
verifyOption('showHexValue', 'boolean', true);
verifyOption('showRawValue', 'boolean', true);
verifyOption('autoClose', 'boolean', true);
var onColourChange = [];
var runOnColourChange = function() {
var text = textColour(colour);
for(var i = 0; i < onColourChange.length; ++i)
onColourChange[i](colour, text);
};
var setColour = function(raw) {
colour = parseInt(raw || 0) & 0xFFFFFF;
runOnColourChange();
};
var apply = function() {
callback(pub, colour);
if(options.autoClose)
close();
};
var cancel = function() {
callback(pub, null);
if(options.autoClose)
close();
};
var close = function() {
if(onClose && onClose())
return;
container.parentNode.removeChild(container);
};
var height = 96;
var container = document.createElement('div');
container.className = 'fw-colour-picker';
onColourChange.push(function(colour) {
container.style.borderColor = hexFormat(colour);
});
var form = document.createElement('form');
form.className = 'fw-colour-picker-form';
container.appendChild(form);
form.onsubmit = function(ev) {
ev.preventDefault();
apply();
return false;
};
var tabs = {};
var activeTab = undefined;
onColourChange.push(function(colour, text) {
if(activeTab) {
activeTab.b.style.background = hexFormat(colour);
activeTab.b.style.borderColor = hexFormat(colour);
activeTab.b.style.color = hexFormat(text);
}
});
var tabbed = document.createElement('div');
tabbed.className = 'fw-colour-picker-tabbed';
var tabbedContainer = document.createElement('div');
tabbedContainer.className = 'fw-colour-picker-tabbed-container';
tabbed.appendChild(tabbedContainer);
onColourChange.push(function(colour) {
tabbedContainer.style.borderColor = hexFormat(colour);
});
var tabbedList = document.createElement('div');
tabbedList.className = 'fw-colour-picker-tabbed-list';
tabbed.appendChild(tabbedList);
var switchTab = function(id) {
if(activeTab == tabs[id])
return;
if(activeTab) {
activeTab.c.classList.remove('fw-colour-picker-tab-container-active');
activeTab.b.classList.remove('fw-colour-picker-tab-button-active');
activeTab.b.style.background = '';
activeTab.b.style.borderColor = '';
activeTab.b.style.color = '';
}
activeTab = tabs[id] || undefined;
if(activeTab) {
activeTab.c.classList.add('fw-colour-picker-tab-container-active');
activeTab.b.classList.add('fw-colour-picker-tab-button-active');
activeTab.b.style.background = hexFormat(colour);
activeTab.b.style.borderColor = hexFormat(colour);
activeTab.b.style.color = hexFormat(textColour(colour));
}
};
var createTab = function(id, name, construct) {
var tabContainer = construct();
tabContainer.className = 'fw-colour-picker-tab-container fw-colour-picker-tab-' + id + '-container';
tabbedContainer.appendChild(tabContainer);
var tabButton = document.createElement('input');
tabButton.type = 'button';
tabButton.value = name;
tabButton.className = 'fw-colour-picker-tab-button fw-colour-picker-tab-' + id + '-button';
tabButton.onclick = function() { switchTab(id); };
tabbedList.appendChild(tabButton);
tabs[id] = { c: tabContainer, b: tabButton };
if(activeTab === undefined) {
activeTab = tabs[id];
tabContainer.className += ' fw-colour-picker-tab-container-active';
tabButton.className += ' fw-colour-picker-tab-button-active';
}
};
if(options.showPresetsTab)
createTab('presets', 'Presets', function() {
var presets = options.presets;
var cont = document.createElement('div');
for(var i = 0; i < presets.length; ++i)
(function(preset) {
var option = document.createElement('a');
option.href = 'javascript:void(0);';
option.className = 'fw-colour-picker-presets-option';
option.style.background = hexFormat(preset.c);
option.title = preset.n;
option.onclick = function() {
setColour(preset.c);
};
onColourChange.push(function(value) {
option.classList[(value === preset.c ? 'add' : 'remove')]('fw-colour-picker-presets-option-active');
});
cont.appendChild(option);
})(presets[i]);
return cont;
});
if(options.showGridTab)
createTab('grid', 'Grid', function() {
var greys = [0xFFFFFF, 0xEBEBEB, 0xD6D6D6, 0xC2C2C2, 0xADADAD, 0x999999, 0x858585, 0x707070, 0x5C5C5C, 0x474747, 0x333333, 0];
var colours = [0x00A1D8, 0x0061FE, 0x4D22B2, 0x982ABC, 0xB92D5D, 0xFF4015, 0xFF6A00, 0xFFAB01, 0xFDC700, 0xFEFB41, 0xD9EC37, 0x76BB40];
var shades = [-.675, -.499, -.345, -.134, 0, .134, .345, .499, .675];
var cont = document.createElement('div');
for(var i = 0; i < greys.length; ++i)
(function(grey) {
var option = document.createElement('a');
option.href = 'javascript:void(0);';
option.className = 'fw-colour-picker-grid-option';
option.style.background = hexFormat(grey);
option.onclick = function() {
setColour(grey);
};
onColourChange.push(function(value) {
option.classList[(value === grey ? 'add' : 'remove')]('fw-colour-picker-grid-option-active');
});
cont.appendChild(option);
})(greys[i]);
for(var i = 0; i < shades.length; ++i)
for(var j = 0; j < colours.length; ++j)
(function(colour) {
var option = document.createElement('a');
option.href = 'javascript:void(0);';
option.className = 'fw-colour-picker-grid-option';
option.style.background = hexFormat(colour);
option.onclick = function() {
setColour(colour);
};
onColourChange.push(function(value) {
option.classList[(value === colour ? 'add' : 'remove')]('fw-colour-picker-grid-option-active');
});
cont.appendChild(option);
})(shadeColour(colours[j], shades[i]));
return cont;
});
if(options.showSlidersTab)
createTab('sliders', 'Sliders', function() {
var cont = document.createElement('div');
var addSlider = function(id, name, update, apply) {
var sCont = document.createElement('div');
sCont.className = 'fw-colour-picker-slider fw-colour-picker-slider-' + id;
cont.appendChild(sCont);
var sName = document.createElement('div');
sName.className = 'fw-colour-picker-slider-name';
sName.textContent = name;
sCont.appendChild(sName);
var sValue = document.createElement('div');
sValue.className = 'fw-colour-picker-slider-value';
sCont.appendChild(sValue);
var sValueSliderCont = document.createElement('div');
sValueSliderCont.className = 'fw-colour-picker-slider-value-slider-container';
sValue.appendChild(sValueSliderCont);
var sGradient = document.createElement('div');
sGradient.className = 'fw-colour-picker-slider-gradient';
sValueSliderCont.appendChild(sGradient);
var sValueSlider = document.createElement('input');
sValueSlider.type = 'range';
if(sValueSlider.type === 'range') {
sValueSlider.className = 'fw-colour-picker-slider-value-slider';
sValueSlider.min = '0';
sValueSlider.max = '255';
sValueSlider.onchange = function() {
setColour(apply(colour, sValueSlider.value));
};
sValueSliderCont.appendChild(sValueSlider);
} else
sValueSlider = undefined;
var sValueInputContainer = document.createElement('div');
sValueInputContainer.className = 'fw-colour-picker-slider-value-input-container';
sValue.appendChild(sValueInputContainer);
var sValueInput = document.createElement('input');
sValueInput.className = 'fw-colour-picker-slider-value-input';
sValueInput.type = 'number';
sValueInput.min = '0';
sValueInput.max = '255';
sValueInput.onchange = function() {
setColour(apply(colour, sValueInput.value));
};
sValueInputContainer.appendChild(sValueInput);
sGradient.onmousedown = function(ev) {
if(ev.button === 0)
setColour(apply(colour, Math.floor(255 * (Math.max(0, Math.min(200, (ev.layerX - 5))) / 200))));
};
onColourChange.push(function(colour) {
if(sValueSlider)
sValueSlider.value = update(colour);
sValueInput.value = update(colour);
var gradient = 'linear-gradient(to right, ' + hexFormat(apply(colour, 0)) + ', ' + hexFormat(apply(colour, 0xFF)) + ')';
sGradient.style.background = '';
sGradient.style.background = gradient;
if(!sGradient.style.background)
sGradient.style.background = '-moz-' + gradient;
if(!sGradient.style.background)
sGradient.style.background = '-webkit-' + gradient;
});
};
addSlider('red', 'Red',
function(value) { return (value >> 16) & 0xFF; },
function(colour, value) { return (colour & 0xFFFF) | (Math.min(255, Math.max(0, value)) << 16); }
);
addSlider('green', 'Green',
function(value) { return (value >> 8) & 0xFF; },
function(colour, value) { return (colour & 0xFF00FF) | (Math.min(255, Math.max(0, value)) << 8); }
);
addSlider('blue', 'Blue',
function(value) { return value & 0xFF; },
function(colour, value) { return (colour & 0xFFFF00) | Math.min(255, Math.max(0, value)); }
);
return cont;
});
if(activeTab) {
height += 261;
form.appendChild(tabbed);
}
var middleRow = document.createElement('div');
middleRow.className = 'fw-colour-picker-middle-row';
form.appendChild(middleRow);
var colourPreviewContainer = document.createElement('div');
colourPreviewContainer.className = 'fw-colour-picker-colour-preview-container';
middleRow.appendChild(colourPreviewContainer);
var colourPreview = document.createElement('div');
colourPreview.className = 'fw-colour-picker-colour-preview';
colourPreviewContainer.appendChild(colourPreview);
onColourChange.push(function(colour) {
colourPreview.style.background = hexFormat(colour);
});
var values = {};
var valuesContainer = document.createElement('div');
valuesContainer.className = 'fw-colour-picker-values-container';
middleRow.appendChild(valuesContainer);
var addValue = function(id, name, type, format, change) {
var valueContainer = document.createElement('label');
valueContainer.className = 'fw-colour-picker-values-child fw-colour-picker-' + id + '-value';
valuesContainer.appendChild(valueContainer);
var valueLabel = document.createElement('div');
valueLabel.textContent = name;
valueLabel.className = 'fw-colour-picker-values-child-label fw-colour-picker-' + id + '-value-label';
valueContainer.appendChild(valueLabel);
var valueInput = document.createElement('input');
valueInput.type = type;
valueInput.value = '0';
valueInput.className = 'fw-colour-picker-values-child-input fw-colour-picker-' + id + '-value-input';
valueInput.onchange = function() {
change(valueInput.value);
};
valueContainer.appendChild(valueInput);
onColourChange.push(function(colour) {
valueInput.value = format(colour);
});
values[id] = { c: valueContainer, l: valueLabel, i: valueInput };
};
if(options.showHexValue)
addValue('hex', 'Hex', 'text', function(value) {
return hexFormat(value);
}, function(value) {
while(value.substring(0, 1) === '#')
value = value.substring(1);
value = value.substring(0, 6);
if(value.length === 3)
value = value.substring(0, 1) + value.substring(0, 1)
+ value.substring(1, 2) + value.substring(1, 2)
+ value.substring(2, 3) + value.substring(2, 3);
if(value.length === 6)
setColour(parseInt(value, 16));
});
if(options.showRawValue)
addValue('raw', 'Raw', 'number', function(value) {
return value;
}, function(value) {
setColour(Math.min(0xFFFFFF, Math.max(0, parseInt(value))));
});
var buttons = {};
var buttonsContainer = document.createElement('div');
buttonsContainer.className = 'fw-colour-picker-buttons-container';
form.appendChild(buttonsContainer);
var buttonsContainerInner = document.createElement('div');
buttonsContainerInner.className = 'fw-colour-picker-buttons-container-inner';
buttonsContainer.appendChild(buttonsContainerInner);
var addButton = function(id, name, action) {
var button = document.createElement('input');
button.className = 'fw-colour-picker-buttons-button fw-colour-picker-buttons-' + id + '-button';
if(action === null) {
button.className += ' fw-colour-picker-buttons-button-submit';
onColourChange.push(function(colour, text) {
button.style.background = hexFormat(colour);
button.style.color = hexFormat(text);
});
button.type = 'submit';
} else {
button.onclick = function() { action(); };
button.type = 'button';
}
button.value = name;
buttonsContainerInner.appendChild(button);
buttons[id] = { b: button };
};
addButton('cancel', 'Cancel', cancel);
addButton('apply', 'Apply', null);
var setPosition = function(x, y) {
container.style.top = y >= 0 ? (y.toString() + 'px') : '';
container.style.left = x >= 0 ? (x.toString() + 'px') : '';
};
setPosition(options.posX, options.posY);
runOnColourChange();
var appendTo = function(parent) {
parent.appendChild(container);
};
var pub = {
getWidth: function() { return 290; },
getHeight: function() { return height; },
getColour: function() { return colour; },
getContainer: function() { return container; },
setColour: setColour,
appendTo: appendTo,
setPosition: setPosition,
switchTab: switchTab,
close: close,
suggestPosition: function(mouseEvent) {
var x = 10, y = 10;
if(document.body.clientWidth > 340) {
x = mouseEvent.clientX;
y = mouseEvent.clientY;
var height = pub.getHeight(),
width = pub.getWidth();
if(y > height + 20)
y -= height;
if(x > document.body.clientWidth - width - 20)
x -= width;
}
return {
x: x,
y: y,
};
},
};
return pub;
};
FwColourPicker.hexFormat = function(raw) {
var str = raw.toString(16).substring(0, 6);
if(str.length < 6)
str = '000000'.substring(str.length) + str;
return '#' + str;
};

View file

@ -0,0 +1,21 @@
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@-o-keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@-moz-keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@-webkit-keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes fadeOutZoom {
0% { opacity: 1; transform: scale(1); }
99% { opacity: 0.0001; transform: scale(20); }
100% { opacity: 0; }
}

134
src/ami.css/domaintrans.css Normal file
View file

@ -0,0 +1,134 @@
.domaintrans {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9001;
font-family: Verdana, Tahoma, Geneva, Arial, Helvetica, sans-serif;
font-size: 16px;
line-height: 20px;
background-color: #222;
color: #ddd;
text-shadow: 0 0 5px #000;
box-shadow: inset 0 0 1em #000;
}
.domaintrans-body {
max-width: 500px;
margin: 20px auto;
}
.domaintrans-domain {
margin: 10px;
width: 100%;
}
.domaintrans-domain-main {
font-size: 1.2em;
line-height: 1.5em;
}
.domaintrans-domain-compat {
font-size: .8em;
line-height: 1.5em;
opacity: .8;
}
.domaintrans-domain-header {
font-size: 1.4em;
line-height: 1.5em;
text-align: center;
}
.domaintrans-domain-display {
width: 100%;
font-size: 1.2em;
line-height: 1.4em;
text-align: center;
}
.domaintrans-domain-display div {
margin: 2px;
display: block;
}
.domaintrans-domain-text div {
display: inline-block;
border: 1px solid #444;
background: #333;
border-radius: 5px;
padding: 2px 5px;
}
.domaintrans-text {
font-size: .8em;
line-height: 1.3em;
margin: 10px auto;
}
.domaintrans-text p {
margin: 1em 10px;
}
.domaintrans-options {
text-align: center;
}
.domaintrans-options > div {
margin: 2px;
display: inline-block;
max-width: 300px;
width: 100%;
}
.domaintrans-option {
display: block;
width: 100%;
color: #fff;
background: #333;
border-radius: 5px;
font-size: 16px;
text-shadow: initial;
text-align: left;
font-family: Verdana, Tahoma, Geneva, Arial, Helvetica, sans-serif;
padding: 7px 15px;
border: 1px solid #444;
box-sizing: border-box;
transition: background .2s;
}
.domaintrans-option .sprite {
margin: 2px;
}
.domaintrans-option-icon {
display: inline-block;
border: 2px solid #aaa;
border-radius: 100%;
vertical-align: middle;
transform: scale(1);
transition: transform .2s;
}
.domaintrans-option-text {
display: inline-block;
vertical-align: middle;
margin-left: 10px;
}
.domaintrans-option:focus {
outline: 2px solid #9475b2;
}
.domaintrans-option:hover,
.domaintrans-option:focus {
background: #3d3d3d;
text-decoration: none;
}
.domaintrans-option:hover .domaintrans-option-icon,
.domaintrans-option:focus .domaintrans-option-icon {
transform: scale(1.2);
}
.domaintrans-option:active {
background: #383838;
}
.domaintrans-option:active .domaintrans-option-icon {
transform: scale(.9);
}

536
src/ami.css/main.css Normal file
View file

@ -0,0 +1,536 @@
* {
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
html,
body {
overflow: hidden;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #000;
color: #fff;
}
body {
z-index: 0;
}
.hidden {
display: none !important;
visibility: hidden !important;
}
#connmsg {
margin: 0;
padding: 0;
font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif;
background: url('/images/clouds-8559a5.jpg');
background-color: #111;
color: #000;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 200;
position: absolute;
text-align: center;
}
#info {
font-size: 1.5em;
line-height: 1.5em;
box-shadow: 0 1px 2px rgba(0, 0, 0, .6);
padding: 15px;
margin: 20px 30px;
background-color: #111;
color: #fff;
vertical-align: top;
display: inline-block;
max-width: 400px;
}
#indicator {
margin: 15px auto 0 auto;
display: block;
background: url('/images/load.gif');
width: 32px;
height: 32px;
}
#indicator img {
vertical-align: middle;
}
.connmsg-exit {
-webkit-animation: 1s ease-in-out forwards fadeOut;
-moz-animation: 1s ease-in-out forwards fadeOut;
-o-animation: 1s ease-in-out forwards fadeOut;
animation: 1s ease-in-out forwards fadeOutZoom;
pointer-events: none;
}
#chat {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#chatTitle {
position: absolute;
left: 20px;
top: 5px;
}
#chatList {
position: absolute;
overflow: auto;
top: 50px;
bottom: 150px;
left: 20px;
}
#chatList .insertImage {
max-width: 70vw;
max-height: 80vh;
vertical-align: top;
overflow: auto;
margin: 5px;
border: 0;
}
#chatList .insertVideo,
#chatList .insertAudio {
vertical-align: top;
overflow: auto;
margin: 5px;
border: 0;
}
#chat .userListVisible {
right: 230px;
}
#chat .wideSideVisible {
right: 390px;
}
#chat .fullWidth {
right: 20px;
}
#chatList .msgBreak {
display: none;
}
#settingsList table,
#helpList table {
width: 100%;
border-collapse: collapse;
}
#settingsList table tr td,
#helpList table tr td {
padding: 4px 10px;
vertical-align: middle;
}
#settingsList .setting {
width: 115px;
}
#settingsList input[type="text"] {
width: 100px;
}
#chatList,
#chat .sidebar {
-webkit-overflow-scrolling: touch;
-ms-scroll-chaining: none;
-ms-touch-action: pan-y;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
#chat .sidebar {
position: absolute;
top: 50px;
right: 20px;
bottom: 150px;
overflow: auto;
width: 200px;
}
#chat .widebar {
width: 360px;
}
#chatList .botError,
#chatList .botMessage,
#chatList .botName {
font-style: italic;
}
#chat .sidebar .top {
width: auto;
padding: 4px 10px;
word-wrap: break-word;
}
#chat .sidebar .body div,
#chatList div {
width: auto;
padding: 2px 10px;
word-wrap: break-word;
}
.alert {
position: absolute;
top: 15px;
margin: 0 auto;
z-index: 999;
left: 0;
right: 0;
width: 25%;
min-width: 300px;
max-width: 500px;
text-align: center;
color: black;
background-color: white;
border: 3px double black;
padding: 15px;
}
#bbCodeContainer input {
margin: 0 3px 0 0;
}
#optionsContainer .optIcon {
display: inline-block;
text-decoration: none;
margin: 0 0 0 5px;
}
#optionsContainer .optIcon:hover {
cursor: pointer;
}
#emotes {
max-height: 30px;
max-width: 600px;
overflow: auto;
}
#emotes img {
margin: 0 3px 0 0;
padding: 0;
image-rendering: crisp-edges;
}
.chatEmote {
vertical-align: middle;
image-rendering: crisp-edges;
}
#emotes img:hover {
cursor: pointer;
}
#chat .userMenu {
margin: 5px 0;
padding-left: 20px;
}
#picker {
position: absolute;
bottom: 20px;
left: 20px;
width: 200px;
height: 200px;
display: none;
border: 1px solid grey;
}
#ppicker {
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 173px;
}
#pslider {
position: absolute;
top: 0;
left: 20px;
width: 180px;
height: 173px;
}
#popts {
position: absolute;
bottom: 0;
left: 0;
width: 200px;
}
#ppickeri {
width: 3px;
height: 3px;
position: absolute;
border: 1px solid white;
}
#pslideri {
width: 18px;
height: 10px;
position: absolute;
border: 1px solid black;
}
#settingsList .mSetting {
display: none;
}
#inputFieldContainer {
position: absolute;
left: 20px;
right: 20px;
bottom: 95px;
padding-right: 4px;
}
#inputFieldContainer table {
margin-bottom: 8px;
}
#message {
width: 100%;
height: 40px;
}
#submitButtonContainer {
position: absolute;
right: 20px;
bottom: 60px;
}
#bbCodeContainer {
position: absolute;
left: 20px;
bottom: 20px;
padding: 3px;
}
#emotes {
position: absolute;
left: 20px;
bottom: 57px;
padding: 3px;
}
#copyright {
position: absolute;
top: 12px;
right: 55px;
font-size: .8em;
text-align: right;
line-height: 1.4em;
}
#copyright p {
margin: 0;
padding: 0;
}
#statusIconContainer {
background-image: url('/images/loading-sprite.png');
position: absolute;
top: 16px;
right: 20px;
width: 22px;
height: 22px;
image-rendering: crisp-edges;
}
#statusIconContainer.status-green {
background-position: 0px 0px;
}
#statusIconContainer.status-yellow {
background-position: 0px -22px;
}
#statusIconContainer.status-red {
background-position: 0px -44px;
}
#optionsContainer {
position: absolute;
right: 20px;
bottom: 20px;
padding: 3px 0 3px 3px;
}
#optionsContainer .mobileOnly {
display: none;
}
@media screen and (max-width: 800px), (max-device-width: 800px) {
body {
z-index: 1;
}
#bbCodeContainer,
#emotes,
#copyright,
#submitButtonContainer,
#statusIconContainer {
display: none;
}
#chat .userListVisible,
#chat .wideSideVisible,
#chat .fullWidth {
right: 10px;
}
#chat .sidebar {
right: 15px;
top: 45px;
bottom: 45px;
}
#settingsList .mSetting {
display: block;
}
#optionsContainer .mobileOnly {
display: inline;
}
#optionsContainer .desktopOnly {
display: none;
}
#chatTitle {
left: 10px;
top: 0;
}
#inputFieldContainer .desktopOnly {
display: none;
}
#optionsContainer {
right: 10px;
top: 7px;
bottom: auto;
}
#inputFieldContainer {
left: 10px;
right: 10px;
bottom: 10px;
}
#inputFieldContainer table {
margin-bottom: 0;
}
#inputFieldContainer #message {
height: 20px;
padding: 0 2px 0 0;
margin: 0;
}
#chatList {
top: 40px;
right: 10px;
left: 10px;
bottom: 40px;
}
}
@media screen and (max-width: 500px), (device-width: 500px) {
#chatList .msgBreak {
display: inline;
}
#chatList .msgColon {
display: none;
}
}
@media screen and (max-width: 400px), (device-width: 400px) {
#chatTitle {
display: none;
}
#chat .widebar {
left: 15px;
width: auto;
}
}
@media screen and (max-width: 235px), (device-width: 235px) {
#chat .sidebar {
left: 15px;
width: auto;
}
}
#chatTitle {
font-family: "Trebuchet MS", Verdana, Arial, sans-serif;
font-size: 1.3em;
font-weight: bold;
margin-left: auto;
margin-top: 12px;
}
.sjis {
font-family: IPAMonaPGothic, 'IPA モナー Pゴシック', Monapo, Mona, 'MS PGothic', ' Pゴシック', monospace;
font-size: 16px;
line-height: 18px;
white-space: pre-wrap;
}
#reconnectIndicator {
position: absolute;
top: 5px;
right: 5px;
background-color: rgba(0, 0, 0, .6);
color: #ff302f;
padding: 5px;
text-align: right;
font-size: .9em;
line-height: 1.2em;
text-shadow: 1px 1px 0 #000;
}
.eepromForm {
text-align: center;
padding: 5px;
}
.eepromHistory {
width: 100%;
border-collapse: collapse;
}
@include animations.css;
@include sprite.css;
@include noscript.css;
@include domaintrans.css;
@include themes/beige.css;
@include themes/black.css;
@include themes/blue.css;
@include themes/cobalt.css;
@include themes/halext.css;
@include themes/legacy.css;
@include themes/lithium.css;
@include themes/mercury.css;
@include themes/mio.css;
@include themes/misuzu.css;
@include themes/nico.css;
@include themes/oxygen.css;
@include themes/sulfur.css;
@include themes/techno.css;
@include themes/white.css;
@include themes/yuuno.css;

58
src/ami.css/noscript.css Normal file
View file

@ -0,0 +1,58 @@
.noscript {
margin: 0;
padding: 0;
font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif;
background: url('/images/clouds-700000.jpg');
background-color: #111;
color: #000;
height: 100%;
position: relative;
width: 100%;
text-align: center;
}
.noscript-background {
position: absolute;
z-index: 100;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('/images/clouds-700000.jpg');
}
.noscript-background-overlay {
width: 100%;
height: 100%;
background: url('/images/clouds-8559a5.jpg');
-webkit-animation: 3s ease-in-out .5s forwards fadeOut;
-moz-animation: 3s ease-in-out .5s forwards fadeOut;
-o-animation: 3s ease-in-out .5s forwards fadeOut;
animation: 3s ease-in-out .5s forwards fadeOutZoom;
}
.noscript-icon div {
margin: 0 auto;
}
.noscript-content {
position: relative;
box-shadow: 0 1px 2px rgba(0, 0, 0, .6);
padding: 15px;
margin: 20px 30px;
background-color: #111;
color: #fff;
vertical-align: top;
display: inline-block;
max-width: 400px;
z-index: 200;
}
.noscript-header {
font-size: 1.5em;
line-height: 1.5em;
}
.noscript-body {
margin-top: 4px;
}
.noscript-body p {
line-height: 1.5em;
}
.noscript-body code {
font-size: 1.3em;
}

31
src/ami.css/sprite.css Normal file
View file

@ -0,0 +1,31 @@
.sprite {
width: 22px !important;
height: 22px !important;
padding: 0 !important;
background-image: url('/images/sprite.png');
background-position: 0 0;
background-size: 154px 44px;
}
.sprite-small {
width: 10px !important;
height: 10px !important;
padding: 0 !important;
background-image: url('/images/sprite.png');
background-position: 0 0;
background-size: 154px 44px;
}
.sprite-user { background-position: 0 0; }
.sprite-uploads { background-position: 0 -22px; }
.sprite-settings { background-position: -22px 0; }
.sprite-help { background-position: -22px -22px; }
.sprite-clear { background-position: -44px 0; }
.sprite-channels { background-position: -44px -22px; }
.sprite-autoscroll { background-position: -66px 0; }
.sprite-autoscroll.off { background-position: -66px -22px; }
.sprite-sound { background-position: -88px 0; }
.sprite-sound.off { background-position: -88px -22px; }
.sprite-warning { background-position: -110px -22px; }
.sprite-export { background-position: -110px 0; }
.sprite-unembed { background-position: -132px -22px; }
.sprite-delete { background-position: -132px 0; }
.sprite-trash { background-position: -132px -10px; }

View file

@ -0,0 +1,107 @@
/* Beige by flashwave <http://flash.moe> */
.ami-themeOpt-beige {
background: #f7f5dc;
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-beige {
background: #f7f5dc;
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-beige #chat a,
.ami-theme-beige #chat a:visited {
color: #000;
}
.ami-theme-beige #chatList a,
.ami-theme-beige #chatList a:visited {
color: #1e90ff;
}
.ami-theme-beige a {
text-decoration: none;
}
.ami-theme-beige a:hover {
text-decoration: underline;
}
.ami-theme-beige #chat select {
color: #000;
background-color: #f7f5dc;
border: 1px solid #c8b360;
}
.ami-theme-beige #chatList {
border: 1px solid #c8b360;
}
.ami-theme-beige #chatList .botName {
color: #9e8da7;
}
.ami-theme-beige #chatList .botError {
color: #f00;
}
.ami-theme-beige .rowEven,
.ami-theme-beige #chat .sidebar .top {
background: #fffff0;
}
.ami-theme-beige .rowOdd {
background: #f7f5dc;
}
.ami-theme-beige .date {
font-size: .7em;
}
.ami-theme-beige #chat .sidebar {
border: 1px solid #c8b360;
background: #f7f5dc;
}
.ami-theme-beige #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-beige #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-beige .alert {
background-color: #f7f5dc;
}
.ami-theme-beige .alert input[type="password"],
.ami-theme-beige .alert input[type="text"],
.ami-theme-beige #chat textarea,
.ami-theme-beige #inputFieldContainer input[type="text"] {
color: #333;
border: 1px solid #c8b360;
}
.ami-theme-beige .alert input[type="password"],
.ami-theme-beige .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-beige #chat input[type="button"],
.ami-theme-beige #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background-color: #d9ce72;
background-image: linear-gradient(to bottom, #e1d995, #d9ce72);
background-image: -moz-linear-gradient(to bottom, #e1d995, #d9ce72);
background-image: -webkit-linear-gradient(to bottom, #e1d995, #d9ce72);
color: #333;
border: 1px solid #c8b360;
}

View file

@ -0,0 +1,103 @@
/* Black by reemo <http://aroltd.com> and flashwave <http://flash.moe> */
.ami-themeOpt-black {
background: #000;
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-black {
background: #000;
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-black #chat a,
.ami-theme-black #chat a:visited {
color: #fff;
}
.ami-theme-black #chatList a,
.ami-theme-black #chatList a:visited {
color: #1e90ff;
}
.ami-theme-black a {
text-decoration: none;
}
.ami-theme-black a:hover {
text-decoration: underline;
}
.ami-theme-black #chat select {
color: #fff;
background-color: #000;
border: 1px solid #808080;
}
.ami-theme-black #chatList {
border: 1px solid #808080;
}
.ami-theme-black #chatList .botName {
color: #9e8da7;
}
.ami-theme-black #chatList .botError {
color: #f00;
}
.ami-theme-black .rowEven,
.ami-theme-black #chat .sidebar .top {
background: #212121;
}
.ami-theme-black .rowOdd {
background: #000;
}
.ami-theme-black .date {
font-size: .7em;
}
.ami-theme-black #chat .sidebar {
border: 1px solid #808080;
background: #000;
}
.ami-theme-black #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-black #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-black .alert input[type="password"],
.ami-theme-black .alert input[type="text"],
.ami-theme-black #chat textarea,
.ami-theme-black #inputFieldContainer input[type="text"] {
color: #fff;
background: #000;
border: 1px solid #808080;
}
.ami-theme-black .alert input[type="password"],
.ami-theme-black .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-black #chat input[type="button"],
.ami-theme-black #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background-color: #000;
background-image: linear-gradient(to bottom, #222, #000);
background-image: -moz-linear-gradient(to bottom, #222, #000);
background-image: -webkit-linear-gradient(to bottom, #222, #000);
color: #f0f0f0;
border: 1px solid #808080;
}

104
src/ami.css/themes/blue.css Normal file
View file

@ -0,0 +1,104 @@
/* Blue by nookls <http://nookls.org> */
.ami-themeOpt-blue {
background: #002545;
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-blue {
background: #002545;
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-blue #chat a,
.ami-theme-blue #chat a:visited {
color: #fff;
}
.ami-theme-blue #chatList a,
.ami-theme-blue #chatList a:visited {
color: #1e90ff;
}
.ami-theme-blue a {
text-decoration: none;
}
.ami-theme-blue a:hover {
text-decoration: underline;
}
.ami-theme-blue #chat select {
color: #fff;
background-color: #002545;
border: 1px solid #808080;
}
.ami-theme-blue #chatList {
border: 1px solid #003d8e;
}
.ami-theme-blue #chatList .botName {
color: #9e8da7;
}
.ami-theme-blue #chatList .botError {
color: #f00;
}
.ami-theme-blue .rowEven,
.ami-theme-blue #chat .sidebar .top {
background: #0d355d;
}
.ami-theme-blue .rowOdd {
background: #002545;
}
.ami-theme-blue .date {
font-size: .7em;
}
.ami-theme-blue #chat .sidebar {
background: #002545;
border: 1px solid #003d8e;
}
.ami-theme-blue #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-blue #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-blue .alert input[type="password"],
.ami-theme-blue .alert input[type="text"],
.ami-theme-blue #chat textarea,
.ami-theme-blue #inputFieldContainer input[type="text"] {
color: #fff;
background: #002545;
border: 1px solid #003d8e;
}
.ami-theme-blue .alert input[type="password"],
.ami-theme-blue .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-blue #chat input[type="button"],
.ami-theme-blue #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background-color: #002545;
background-image: linear-gradient(to bottom, #002545, #000);
background-image: -moz-linear-gradient(to bottom, #002545, #000);
background-image: -webkit-linear-gradient(to bottom, #002545, #000);
color: #f0f0f0;
border: 1px solid #808080;
}

View file

@ -0,0 +1,104 @@
/* Cobalt by flashwave <http://flash.moe>, colour palette inspired by PunBB style "Cobalt": http://punbb.org/ */
.ami-themeOpt-cobalt {
background: #2a2a2a;
color: #d4d4d4;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-cobalt {
background: #2a2a2a;
color: #d4d4d4;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-cobalt #chat a,
.ami-theme-cobalt #chat a:visited {
color: #d4d4d4;
}
.ami-theme-cobalt #chatList a,
.ami-theme-cobalt #chatList a:visited {
color: #60a0dc;
}
.ami-theme-cobalt a {
text-decoration: none;
}
.ami-theme-cobalt a:hover {
text-decoration: underline;
}
.ami-theme-cobalt #chat select {
color: #ababab;
background-color: #383838;
border: 1px solid #565656;
}
.ami-theme-cobalt #chatList {
border: 1px solid #565656;
background: #383838;
}
.ami-theme-cobalt #chatList .botName {
color: #9e8da7;
}
.ami-theme-cobalt #chatList .botError {
color: #f00;
}
.ami-theme-cobalt .rowEven,
.ami-theme-cobalt #chat .sidebar .top {
background: #565656;
}
.ami-theme-cobalt .rowOdd {
background: #484848;
}
.ami-theme-cobalt .date {
font-size: .7em;
}
.ami-theme-cobalt #chat .sidebar {
border: 1px solid #565656;
background: #383838;
}
.ami-theme-cobalt #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
background: #383838;
color: #d4d4d4;
}
.ami-theme-cobalt #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-cobalt .alert input[type="password"],
.ami-theme-cobalt .alert input[type="text"],
.ami-theme-cobalt #chat textarea,
.ami-theme-cobalt #inputFieldContainer input[type="text"] {
color: #ababab;
background: #383838;
border: 1px solid #565656;
}
.ami-theme-cobalt .alert input[type="password"],
.ami-theme-cobalt .alert input[type="text"] {
padding: 4px 2px;
}
.ami-theme-cobalt #chat input[type="button"],
.ami-theme-cobalt #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background: #1a1a1a;
color: #ababab;
border: 0px;
}

View file

@ -0,0 +1,108 @@
/* Halext by freakyfurball <http://halext.org> and flashwave <http://flash.moe> */
.ami-themeOpt-halext {
background-color: #4c3b52;
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-halext {
background-color: #000;
background-image: linear-gradient(to bottom, #4c3b52, #000);
background-image: -moz-linear-gradient(to bottom, #4c3b52, #000);
background-image: -webkit-linear-gradient(to bottom, #4c3b52, #000);
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-halext #chat a,
.ami-theme-halext #chat a:visited {
color: #9775a3;
}
.ami-theme-halext #chatList a,
.ami-theme-halext #chatList a:visited {
color: #9775a3;
}
.ami-theme-halext a {
text-decoration: none;
}
.ami-theme-halext a:hover {
text-decoration: underline;
}
.ami-theme-halext #chat select {
color: #999;
background-color: #312634;
border: 1px solid #808080;
}
.ami-theme-halext #chatList {
border: 1px solid #808080;
background: #231b26;
}
.ami-theme-halext #chatList .botName {
color: #9e8da7;
}
.ami-theme-halext #chatList .botError {
color: #f00;
}
.ami-theme-halext .rowEven,
.ami-theme-halext #chat .sidebar .top {
background: #312634;
}
.ami-theme-halext .rowOdd {
background: #4c3b52;
}
.ami-theme-halext .date {
font-size: .7em;
}
.ami-theme-halext #chat .sidebar {
border: 1px solid #808080;
background: #231b26;
}
.ami-theme-halext #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-halext #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-halext .alert input[type="password"],
.ami-theme-halext .alert input[type="text"],
.ami-theme-halext #chat textarea,
.ami-theme-halext #inputFieldContainer input[type="text"] {
color: #999999;
background: #312634;
border: 1px solid #808080;
}
.ami-theme-halext .alert input[type="password"],
.ami-theme-halext .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-halext #chat input[type="button"],
.ami-theme-halext #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background-color: #000;
background-image: linear-gradient(to bottom, #231b26, #000);
background-image: -moz-linear-gradient(to bottom, #231b26, #000);
background-image: -webkit-linear-gradient(to bottom, #231b26, #000);
color: #999;
border: 1px solid #808080;
}

View file

@ -0,0 +1,107 @@
/* Legacy by flashwave <http://flash.moe> and reemo <http://aroltd.com> */
.ami-themeOpt-legacy {
background: #000;
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-legacy {
background: #000;
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-legacy #chat a,
.ami-theme-legacy #chat a:visited {
color: #fff;
}
.ami-theme-legacy #chatList a,
.ami-theme-legacy #chatList a:visited {
color: #1e90ff;
}
.ami-theme-legacy a {
text-decoration: none;
}
.ami-theme-legacy a:hover {
text-decoration: underline;
}
.ami-theme-legacy #chat select {
color: #fff;
background-color: #212121;
}
.ami-theme-legacy #chatList {
border: 1px solid #808080;
}
.ami-theme-legacy #chatList .botName {
color: #9e8da7;
}
.ami-theme-legacy #chatList .botError {
color: #f00;
}
.ami-theme-legacy .rowEven,
.ami-theme-legacy #chat .sidebar .top {
background: #212121;
}
.ami-theme-legacy .rowOdd {
background: #000;
}
.ami-theme-legacy .date {
font-size: .7em;
}
.ami-theme-legacy #chat .sidebar {
border: 1px solid #808080;
background: #000;
}
.ami-theme-legacy #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-legacy #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-legacy .alert input[type="password"],
.ami-theme-legacy .alert input[type="text"],
.ami-theme-legacy #chat textarea,
.ami-theme-legacy #inputFieldContainer input[type="text"] {
color: #fff;
background: #212121;
}
.ami-theme-legacy .alert input[type="password"],
.ami-theme-legacy .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-legacy #chat input[type="button"],
.ami-theme-legacy #chat input[type="submit"] {
cursor: pointer;
background: #212121;
color: #f0f0f0;
padding: 1px 5px;
}
.ami-theme-legacy #emotes,
.ami-theme-legacy #bbCodeContainer {
border: 1px solid #808080;
}
.ami-theme-legacy #bbCodeContainer input:last-child {
margin: 0 !important;
}

View file

@ -0,0 +1,107 @@
/* Lithium by flashwave <http://flash.moe>, colour palette inspired by PunBB style "Lithium": http://punbb.org/ */
.ami-themeOpt-lithium {
background: #f1f1f1;
color: #333;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-lithium {
background: #f1f1f1;
color: #333;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-lithium #chat a,
.ami-theme-lithium #chat a:visited {
color: #638137;
}
.ami-theme-lithium #chatList a,
.ami-theme-lithium #chatList a:visited {
color: #638137;
}
.ami-theme-lithium a {
text-decoration: none;
}
.ami-theme-lithium a:hover {
text-decoration: underline;
}
.ami-theme-lithium #chat select {
color: #333;
background-color: #fff;
border: 1px solid #6c8a3f;
}
.ami-theme-lithium #chatList {
border: 1px solid #6c8a3f;
}
.ami-theme-lithium #chatList .botName {
color: #9e8da7;
}
.ami-theme-lithium #chatList .botError {
color: #f00;
}
.ami-theme-lithium .rowEven,
.ami-theme-lithium #chat .sidebar .top {
background: #f1f1f1;
}
.ami-theme-lithium .rowOdd {
background: #dedfdf;
}
.ami-theme-lithium .date {
font-size: .7em;
}
.ami-theme-lithium #chat .sidebar {
background: #f1f1f1;
border: 1px solid #6c8a3f;
}
.ami-theme-lithium #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
background: #6c8a3f;
color: #fff;
}
.ami-theme-lithium #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-lithium .alert input[type="password"],
.ami-theme-lithium .alert input[type="text"],
.ami-theme-lithium #chat textarea,
.ami-theme-lithium #inputFieldContainer input[type="text"] {
color: #333;
border: 1px solid #6c8a3f;
}
.ami-theme-lithium .alert input[type="password"],
.ami-theme-lithium .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-lithium #chat input[type="button"],
.ami-theme-lithium #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background: #6c8a3f;
color: #fff;
border: 1px solid #6c8a3f;
}
.ami-theme-lithium #chat input[type="button"]:hover,
.ami-theme-lithium #chat input[type="submit"]:hover {
background: #5e7d2f;
}

View file

@ -0,0 +1,104 @@
/* Mercury by flashwave <http://flash.moe>, colour palette inspired by PunBB style "Mercury": http://punbb.org/ */
.ami-themeOpt-mercury {
background: #2a2a2a;
color: #d4d4d4;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-mercury {
background: #2a2a2a;
color: #d4d4d4;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-mercury #chat a,
.ami-theme-mercury #chat a:visited {
color: #f6b620;
}
.ami-theme-mercury #chatList a,
.ami-theme-mercury #chatList a:visited {
color: #f6b620;
}
.ami-theme-mercury a {
text-decoration: none;
}
.ami-theme-mercury a:hover {
text-decoration: underline;
}
.ami-theme-mercury #chat select {
color: #ababab;
background-color: #383838;
border: 1px solid #565656;
}
.ami-theme-mercury #chatList {
border: 1px solid #565656;
background: #383838;
}
.ami-theme-mercury #chatList .botName {
color: #9e8da7;
}
.ami-theme-mercury #chatList .botError {
color: #f00;
}
.ami-theme-mercury .rowEven,
.ami-theme-mercury #chat .sidebar .top {
background: #505050;
}
.ami-theme-mercury .rowOdd {
background: #484848;
}
.ami-theme-mercury .date {
font-size: .7em;
}
.ami-theme-mercury #chat .sidebar {
border: 1px solid #565656;
background: #383838;
}
.ami-theme-mercury #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
background: #383838;
color: #d4d4d4;
}
.ami-theme-mercury #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-mercury .alert input[type="password"],
.ami-theme-mercury .alert input[type="text"],
.ami-theme-mercury #chat textarea,
.ami-theme-mercury #inputFieldContainer input[type="text"] {
color: #ababab;
background: #383838;
border: 1px solid #565656;
}
.ami-theme-mercury .alert input[type="password"],
.ami-theme-mercury .alert input[type="text"] {
padding: 4px 2px;
}
.ami-theme-mercury #chat input[type="button"],
.ami-theme-mercury #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background: #1a1a1a;
color: #ababab;
border: 0;
}

124
src/ami.css/themes/mio.css Normal file
View file

@ -0,0 +1,124 @@
/* Mio by flashwave <http://flash.moe> */
.ami-themeOpt-mio {
background: #9475b2;
color: #306;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-mio {
background-color: #fbeeff;
background-image: url('/images/fade-purple.png');
background-repeat: repeat-x;
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-mio #chatTitle {
color: #306;
}
.ami-theme-mio #chat a,
.ami-theme-mio #chat a:visited {
color: #000;
}
.ami-theme-mio #chatList a,
.ami-theme-mio #chatList a:visited {
color: #1e90ff;
}
.ami-theme-mio a {
text-decoration: none;
}
.ami-theme-mio a:hover {
text-decoration: underline;
}
.ami-theme-mio #chat select {
color: #000;
background-color: #fff;
border: 1px solid #888;
}
.ami-theme-mio #chat select:focus {
border-color: #9475b2;
}
.ami-theme-mio #chatList {
border: 1px solid #9475b2;
}
.ami-theme-mio #chatList .botName {
color: #9e8da7;
}
.ami-theme-mio #chatList .botError {
color: #f00;
}
.ami-theme-mio .rowEven {
background: #c9bbcc;
}
.ami-theme-mio .rowOdd {
background: #fbeeff;
}
.ami-theme-mio #chat .sidebar .top {
background: #9475b2;
color: #306;
}
.ami-theme-mio .date {
font-size: .7em;
}
.ami-theme-mio #chat .sidebar {
border: 1px solid #9475b2;
background: #fbeeff;
}
.ami-theme-mio #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-mio #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-mio .alert input[type="password"],
.ami-theme-mio .alert input[type="text"],
.ami-theme-mio #chat textarea,
.ami-theme-mio #inputFieldContainer input[type="text"] {
color: #000;
background: #fff;
border: 1px solid #888;
}
.ami-theme-mio .alert input[type="password"]:focus,
.ami-theme-mio .alert input[type="text"]:focus,
.ami-theme-mio #chat textarea:focus,
.ami-theme-mio #inputFieldContainer input[type="text"]:focus {
border-color: #9475b2;
}
.ami-theme-mio .alert input[type="password"],
.ami-theme-mio .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-mio #chat input[type="button"],
.ami-theme-mio #chat input[type="submit"] {
cursor: pointer;
padding: 1px 5px;
}
.ami-theme-mio #emotes,
.ami-theme-mio #bbCodeContainer {
border: 1px solid #9475b2;
}
.ami-theme-mio #bbCodeContainer input:last-child {
margin: 0 !important;
}

View file

@ -0,0 +1,159 @@
/* Misuzu by flashwave <https://flash.moe> */
.ami-themeOpt-misuzu {
background-color: #8559a5;
color: #fff;
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
}
.ami-theme-misuzu {
background: url('/images/clouds-8559a5.jpg');
color: #fff;
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-misuzu #chat a,
.ami-theme-misuzu #chat a:visited {
color: #fff;
}
.ami-theme-misuzu #chatList a,
.ami-theme-misuzu #chatList a:visited {
color: #1e90ff;
}
.ami-theme-misuzu a {
text-decoration: none;
}
.ami-theme-misuzu a:hover {
text-decoration: underline;
}
.ami-theme-misuzu #chat select {
border: 1px solid #222;
background: #222;
color: #fff;
border-radius: 2px;
box-shadow: inset 0 0 4px #111;
transition: border-color .2s;
}
.ami-theme-misuzu #chat select:focus {
border-color: #8559a5;
}
.ami-theme-misuzu #chatList {
border: 1px solid #161616;
background-color: #161616;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.ami-theme-misuzu #emotes {
border: 1px solid #161616;
background-color: #161616;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.ami-theme-misuzu #chatList .botName {
color: #9e8da7;
}
.ami-theme-misuzu #chatList .botError {
color: #f00;
}
.ami-theme-misuzu .rowOdd {
background: #181818;
}
.ami-theme-misuzu .rowEven {
background: #131313;
}
.ami-theme-misuzu #chat .sidebar .top {
background-color: #111;
background-image: linear-gradient(to bottom, #8559a5, #181818);
background-image: -moz-linear-gradient(to bottom, #8559a5, #181818);
background-image: -webkit-linear-gradient(to bottom, #8559a5, #181818);
color: #fff;
}
.ami-theme-misuzu .date {
font-size: .7em;
}
.ami-theme-misuzu #chat .sidebar {
border: 1px solid #161616;
background-color: #161616;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.ami-theme-misuzu #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-misuzu #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-misuzu .alert input[type="password"],
.ami-theme-misuzu .alert input[type="text"],
.ami-theme-misuzu #inputFieldContainer input[type="text"] {
color: #fff;
background: #000;
border: 1px solid #808080;
}
.ami-theme-misuzu #chat textarea {
color: #fff;
border: 1px solid #161616;
background-color: #161616;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.ami-theme-misuzu .alert input[type="password"],
.ami-theme-misuzu .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-misuzu #chat input[type="button"],
.ami-theme-misuzu #chat input[type="submit"] {
background-color: #161616;
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
text-align: center;
cursor: pointer;
transition: color .2s, background-color .2s;
padding: 4px 10px;
border: 1px solid #8559a5;
color: #8559a5;
border-radius: 2px;
}
.ami-theme-misuzu #chat input[type="button"]:hover,
.ami-theme-misuzu #chat input[type="submit"]:hover
.ami-theme-misuzu #chat input[type="button"]:focus,
.ami-theme-misuzu #chat input[type="submit"]:focus
.ami-theme-misuzu #chat input[type="button"]:active,
.ami-theme-misuzu #chat input[type="submit"]:active {
color: #111;
background-color: #8559a5;
}
.ami-theme-misuzu #statusIconContainer {
background-image: url('/images/loading-sprite-link.png');
position: absolute;
top: 14px;
right: 20px;
width: 25px;
height: 26px;
}
.ami-theme-misuzu #statusIconContainer.status-green {
background-position: 0px 0px;
}
.ami-theme-misuzu #statusIconContainer.status-yellow {
background-position: 0px -26px;
}
.ami-theme-misuzu #statusIconContainer.status-red {
background-position: 0px -52px;
}

112
src/ami.css/themes/nico.css Normal file
View file

@ -0,0 +1,112 @@
/* NicoFlashii by flashwave <http://flash.moe> and reemo <http://aroltd.com> */
.ami-themeOpt-nico {
background-color: #110033;
color: #fff;
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
}
.ami-theme-nico {
background: url('/images/clouds-legacy.jpg') #000;
color: #fff;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-nico #chat a,
.ami-theme-nico #chat a:visited {
color: #fff;
}
.ami-theme-nico #chatList a,
.ami-theme-nico #chatList a:visited {
color: #1e90ff;
}
.ami-theme-nico a {
text-decoration: none;
}
.ami-theme-nico a:hover {
text-decoration: underline;
}
.ami-theme-nico #chat select {
color: #fff;
background-color: #000;
border: 1px solid #808080;
}
.ami-theme-nico #chatList {
border: 1px solid #808080;
}
.ami-theme-nico #chatList .botName {
color: #9e8da7;
}
.ami-theme-nico #chatList .botError {
color: #f00;
}
.ami-theme-nico .date {
font-size: .7em;
}
.ami-theme-nico #chat .sidebar {
border: 1px solid #808080;
}
.ami-theme-nico #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-nico #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-nico .alert input[type="password"],
.ami-theme-nico .alert input[type="text"],
.ami-theme-nico #chat textarea,
.ami-theme-nico #inputFieldContainer input[type="text"] {
color: #fff;
background: #000;
border: 1px solid #808080;
}
.ami-theme-nico .alert input[type="password"],
.ami-theme-nico .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-nico #chat input[type="button"],
.ami-theme-nico #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background-color: #110033;
background-image: linear-gradient(to bottom, #110033, #000);
background-image: -moz-linear-gradient(to bottom, #110033, #000);
background-image: -webkit-linear-gradient(to bottom, #110033, #000);
color: #f0f0f0;
border: 1px solid #808080;
}
.ami-theme-nico #statusIconContainer {
background-image: url('/images/loading-sprite-link.png');
position: absolute;
top: 14px;
right: 20px;
width: 25px;
height: 26px;
}
.ami-theme-nico #statusIconContainer.status-green {
background-position: 0px 0px;
}
.ami-theme-nico #statusIconContainer.status-yellow {
background-position: 0px -26px;
}
.ami-theme-nico #statusIconContainer.status-red {
background-position: 0px -52px;
}

View file

@ -0,0 +1,105 @@
/* Oxygen by flashwave <http://flash.moe>, colour palette inspired by PunBB style "Oxygen": http://punbb.org/ */
.ami-themeOpt-oxygen {
background: #f1f1f1;
color: #0066b9;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-oxygen {
background: #f1f1f1;
color: #333;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-oxygen #chat a,
.ami-theme-oxygen #chat a:visited {
color: #0066b9;
}
.ami-theme-oxygen #chatList a,
.ami-theme-oxygen #chatList a:visited {
color: #0066b9;
}
.ami-theme-oxygen a {
text-decoration: none;
}
.ami-theme-oxygen a:hover {
text-decoration: underline;
}
.ami-theme-oxygen #chat select {
border: 1px solid #0066b9;
}
.ami-theme-oxygen #chatList {
border: 1px solid #0066b9;
}
.ami-theme-oxygen #chatList .botName {
color: #9e8da7;
}
.ami-theme-oxygen #chatList .botError {
color: #f00;
}
.ami-theme-oxygen .rowEven,
.ami-theme-oxygen #chat .sidebar .top {
background: #f1f1f1;
}
.ami-theme-oxygen .rowOdd {
background: #e6e7e7;
}
.ami-theme-oxygen .date {
font-size: .7em;
}
.ami-theme-oxygen #chat .sidebar {
background: #f1f1f1;
border: 1px solid #0066b9;
}
.ami-theme-oxygen #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
background-color: #0066b9;
background-image: linear-gradient(to bottom, #2a6ab8, #4795cc);
background-image: -moz-linear-gradient(to bottom, #2a6ab8, #4795cc);
background-image: -webkit-linear-gradient(to bottom, #2a6ab8, #4795cc);
color: #fff;
}
.ami-theme-oxygen #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-oxygen .alert input[type="password"],
.ami-theme-oxygen .alert input[type="text"],
.ami-theme-oxygen #chat textarea,
.ami-theme-oxygen #inputFieldContainer input[type="text"] {
border: 1px solid #0066b9;
}
.ami-theme-oxygen .alert input[type="password"],
.ami-theme-oxygen .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-oxygen #chat input[type="button"],
.ami-theme-oxygen #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background-color: #0066b9;
background-image: linear-gradient(to bottom, #4795cc, #2a6ab8);
background-image: -moz-linear-gradient(to bottom, #4795cc, #2a6ab8);
background-image: -webkit-linear-gradient(to bottom, #4795cc, #2a6ab8);
color: #FFF;
border: 1px solid #0066b9;
}

View file

@ -0,0 +1,99 @@
/* Sulfur by flashwave <http://flash.moe>, colour palette inspired by PunBB style "Sulfur": http://punbb.org/ */
.ami-themeOpt-sulfur {
background: #f1f1f1;
color: #b84623;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-sulfur {
background: #f1f1f1;
color: #333;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-sulfur #chat a,
.ami-theme-sulfur #chat a:visited {
color: #b84623;
}
.ami-theme-sulfur #chatList a,
.ami-theme-sulfur #chatList a:visited {
color: #b84623;
}
.ami-theme-sulfur a {
text-decoration: none;
}
.ami-theme-sulfur a:hover {
text-decoration: underline;
}
.ami-theme-sulfur #chat select {
border: 1px solid #b84623;
}
.ami-theme-sulfur #chatList {
border: 1px solid #b84623;
}
.ami-theme-sulfur #chatList .botName {
color: #9e8da7;
}
.ami-theme-sulfur #chatList .botError {
color: #f00;
}
.ami-theme-sulfur .rowEven,
.ami-theme-sulfur #chat .sidebar .top {
background: #f1f1f1;
}
.ami-theme-sulfur .rowOdd {
background: #dedfdf;
}
.ami-theme-sulfur .date {
font-size: .7em;
}
.ami-theme-sulfur #chat .sidebar {
background: #f1f1f1;
border: 1px solid #b84623;
}
.ami-theme-sulfur #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
background: #b84623;
color: #fff;
}
.ami-theme-sulfur #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-sulfur .alert input[type="password"],
.ami-theme-sulfur .alert input[type="text"],
.ami-theme-sulfur #chat textarea,
.ami-theme-sulfur #inputFieldContainer input[type="text"] {
border: 1px solid #b84623;
}
.ami-theme-sulfur .alert input[type="password"],
.ami-theme-sulfur .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-sulfur #chat input[type="button"],
.ami-theme-sulfur #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background: #b84623;
color: #fff;
border: 1px solid #b84623;
}

View file

@ -0,0 +1,101 @@
/* Techno by bribob4 */
.ami-themeOpt-techno {
background: #000;
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-techno {
background: #000;
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-techno #chat a,
.ami-theme-techno #chat a:visited {
color: #000;
}
.ami-theme-techno #chatList a,
.ami-theme-techno #chatList a:visited {
color: #000;
}
.ami-theme-techno a {
text-decoration: none;
}
.ami-theme-techno a:hover {
text-decoration: underline;
}
.ami-theme-techno #chat select {
color: #000;
background-color: #000;
border: 1px solid #000;
}
.ami-theme-techno #chatList {
border: 1px solid #000;
}
.ami-theme-techno #chatList .botName {
color: #000;
}
.ami-theme-techno #chatList .botError {
color: #000;
}
.ami-theme-techno .rowEven,
.ami-theme-techno #chat .sidebar .top {
background: #000;
}
.ami-theme-techno .rowOdd {
background: #000;
}
.ami-theme-techno .date {
font-size: .7em;
}
.ami-theme-techno #chat .sidebar {
border: 1px solid #000;
background: #000;
}
.ami-theme-techno #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-techno #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-techno .alert input[type="password"],
.ami-theme-techno .alert input[type="text"],
.ami-theme-techno #chat textarea,
.ami-theme-techno #inputFieldContainer input[type="text"] {
color: #000;
background: #000;
border: 1px solid #000;
}
.ami-theme-techno .alert input[type="password"],
.ami-theme-techno .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-techno #chat input[type="button"],
.ami-theme-techno #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background: #000;
color: #000;
border: 1px solid #000;
}

View file

@ -0,0 +1,104 @@
/* White by reemo <http://aroltd.com> and flashwave <http://flash.moe> */
.ami-themeOpt-white {
background: #fff;
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-white {
background: #fff;
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
}
.ami-theme-white #chat a,
.ami-theme-white #chat a:visited {
color: #000;
}
.ami-theme-white #chatList a,
.ami-theme-white #chatList a:visited {
color: #1e90ff;
}
.ami-theme-white a {
text-decoration: none;
}
.ami-theme-white a:hover {
text-decoration: underline;
}
.ami-theme-white #chat select {
color: #000;
background-color: #fff;
border: 1px solid #808080;
}
.ami-theme-white #chatList {
border: 1px solid #808080;
}
.ami-theme-white #chatList .botName {
color: #9e8da7;
}
.ami-theme-white #chatList .botError {
color: #f00;
}
.ami-theme-white .rowEven,
.ami-theme-white #chat .sidebar .top {
background: #dedede;
}
.ami-theme-white .rowOdd {
background: #fff;
}
.ami-theme-white .date {
font-size: .7em;
}
.ami-theme-white #chat .sidebar {
background: #fff;
border: 1px solid #808080;
}
.ami-theme-white #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
}
.ami-theme-white #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-white .alert input[type="password"],
.ami-theme-white .alert input[type="text"],
.ami-theme-white #chat textarea,
.ami-theme-white #inputFieldContainer input[type="text"] {
color: #000;
background: #fff;
border: 1px solid #808080;
}
.ami-theme-white .alert input[type="password"],
.ami-theme-white .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-white #chat input[type="button"],
.ami-theme-white #chat input[type="submit"] {
cursor: pointer;
padding: 4px 10px;
background-color: #fff;
background-image: linear-gradient(to bottom, #fff, #ddd);
background-image: -moz-linear-gradient(to bottom, #fff, #ddd);
background-image: -webkit-linear-gradient(to bottom, #fff, #ddd);
color: #0f0f0f;
border: 1px solid #808080;
}

View file

@ -0,0 +1,137 @@
/* Yuuno by flashwave <http://flash.moe> */
.ami-themeOpt-yuuno {
background-color: #c2affe;
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
.ami-theme-yuuno {
background-color: #fbeeff;
background-image: linear-gradient(to bottom, #c2affe, #fbeeff);
background-image: -moz-linear-gradient(to bottom, #c2affe, #fbeeff);
background-image: -webkit-linear-gradient(to bottom, #c2affe, #fbeeff);
color: #000;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: .8em;
height: 100%;
width: 100%;
}
.ami-theme-yuuno a,
.ami-theme-yuuno a:visited {
color: #22e;
text-decoration: none;
}
.ami-theme-yuuno a:hover {
text-decoration: underline;
}
.ami-theme-yuuno a:active {
color: #e22;
}
.ami-theme-yuuno #chatTitle {
color: #643B8C;
}
.ami-theme-yuuno #chat select {
background-color: #fff;
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%, #e5e5e5 50%);
background-image: -moz-linear-gradient(to bottom, #fff 0%, #eee 50%, #e5e5e5 50%);
background-image: -webkit-linear-gradient(to bottom, #fff 0%, #eee 50%, #e5e5e5 50%);
border: 1px solid #ccc;
}
.ami-theme-yuuno #chatList {
border: 1px solid #9475b2;
box-shadow: 0 0 3px #9475b2;
}
.ami-theme-yuuno #chatList .botName {
color: #9e8da7;
}
.ami-theme-yuuno #chatList .botError {
color: #f00;
}
.ami-theme-yuuno .rowEven,
.ami-theme-yuuno #chat .sidebar .top {
background: #d3bfff;
}
.ami-theme-yuuno .rowOdd {
background: #c2affe;
}
.ami-theme-yuuno .date {
font-size: .7em;
}
.ami-theme-yuuno #chat .sidebar {
border: 1px solid #9475b2;
box-shadow: 0 0 3px #9475b2;
}
.ami-theme-yuuno #chat .sidebar .top {
font-size: 1em;
text-align: center;
font-weight: bold;
color: #643b8c;
background-color: #9475b2;
background-image: linear-gradient(to right, #9475b2, #c2affe);
background-image: -moz-linear-gradient(to right, #9475b2, #c2affe);
background-image: -webkit-linear-gradient(to right, #9475b2, #c2affe);
}
.ami-theme-yuuno #chat .sidebar .body {
font-size: .9em;
}
.ami-theme-yuuno .alert input[type="password"],
.ami-theme-yuuno .alert input[type="text"],
.ami-theme-yuuno #chat textarea,
.ami-theme-yuuno #inputFieldContainer input[type="text"] {
box-shadow: 0 0 3px #9475b2, inset #ddd 0 0 5px;
background-color: #fff;
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%, #e5e5e5 50%);
background-image: -moz-linear-gradient(to bottom, #fff 0%, #eee 50%, #e5e5e5 50%);
background-image: -webkit-linear-gradient(to bottom, #fff 0%, #eee 50%, #e5e5e5 50%);
border: 1px solid #ccc;
}
.ami-theme-yuuno .alert input[type="password"],
.ami-theme-yuuno .alert input[type="text"] {
padding: 5px 2px;
}
.ami-theme-yuuno #chat input[type="button"],
.ami-theme-yuuno #chat input[type="submit"] {
box-shadow: inset #222 0 0 1px;
text-shadow: #888 0 0 2px;
cursor: pointer;
padding: 3px 10px;
background-color: #9475b2;
background-image: linear-gradient(to bottom, #9475b2 0%, #9475b2 50%, #86a 50%);
background-image: -moz-linear-gradient(to bottom, #9475b2 0%, #9475b2 50%, #86a 50%);
background-image: -webkit-linear-gradient(to bottom, #9475b2 0%, #9475b2 50%, #86a 50%);
border-radius: 3px;
color: #f0f0f0;
border: 0;
transition: text-shadow .5s, box-shadow .5s;
}
.ami-theme-yuuno #chat input[type="button"]:hover,
.ami-theme-yuuno #chat input[type="submit"]:hover {
box-shadow: inset #222 0 0 3px;
text-shadow: #f1f1f1 0 0 5px;
}
.ami-theme-yuuno #chat input[type="button"]:active,
.ami-theme-yuuno #chat input[type="submit"]:active {
box-shadow: inset #222 0 0 5px;
text-shadow: #f1f1f1 0 0 3px;
transition: text-shadow .2s, box-shadow .2s;
}

30
src/ami.html Normal file
View file

@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="format-detection" content="telephone=no">
<link href="{ami.css}" rel="stylesheet" type="text/css">
<link href="/picker.css" rel="stylesheet" type="text/css">
</head>
<body>
<noscript>
<div class="noscript">
<div class="noscript-background">
<div class="noscript-background-overlay"></div>
</div>
<div class="noscript-content">
<div class="noscript-header">Enable Javascript!</div>
<div class="noscript-body">
<p>{title} is a web based chat and requires Javascript to work.</p>
<p>If you use any other privacy tools that prevent loading content from other domains, you may also need to white list <code>static.flash.moe</code> as many resources are loaded from there.</p>
</div>
<div class="noscript-icon"><div class="sprite sprite-warning"></div></div>
</div>
</div>
</noscript>
<script src="/picker.js" type="text/javascript" charset="utf-8"></script>
<script src="{ami.js}" type="text/javascript" charset="utf-8"></script>
</body>
</html>

70
src/ami.js/buttons.js Normal file
View file

@ -0,0 +1,70 @@
#include utility.js
var AmiOptionButtons = function(parent) {
var container = $e({ attrs: { id: 'bbCodeContainer' } }),
buttons = new Map;
parent.appendChild(container);
var createBtn = function(name, title, languageKey, style, onclick) {
var pub = {};
var elem = $e({
tag: 'input',
attrs: {
type: 'button',
name: typeof languageKey === 'string' ? languageKey : ';;',
value: title,
style: style,
onclick: function(ev) { performClick(ev); },
},
});
var hide = function() { elem.classList.add('hidden'); };
var show = function() { elem.classList.remove('hidden'); };
var performClick = function(ev) {
onclick.call(pub, pub, ev);
};
pub.getName = function() { return name; };
pub.getTitle = function() { return title; };
pub.getLanguageKey = function() { return languageKey; };
pub.getElement = function() { return elem; };
pub.performClick = performClick;
pub.hide = hide;
pub.show = show;
return pub;
};
var hide = function() { container.classList.add('hidden'); };
var show = function() { container.classList.remove('hidden'); };
return {
create: function(name, title, languageKey, style, onclick) {
if(buttons.has(name))
throw 'a button with this name already exists';
var btnInfo = createBtn(name, title, languageKey, style, onclick);
buttons.set(btnInfo.getName(), btnInfo);
container.appendChild(btnInfo.getElement());
return btnInfo;
},
has: function(name) {
return buttons.has(name);
},
get: function(name) {
return buttons.get(name);
},
remove: function(name) {
if(!buttons.has(name))
return;
var btnInfo = buttons.get(name);
container.removeChild(btnInfo.getElement());
buttons.delete(name);
},
clear: function() {
buttons.clear();
$rc(container);
},
hide: hide,
show: show,
};
};

149
src/ami.js/chat.js Normal file
View file

@ -0,0 +1,149 @@
#include buttons.js
#include colourpick.js
#include emotes.js
#include messages.js
#include inputbox.js
#include opticons.js
#include sidebars.js
#include status.js
#include submitbox.js
#include title.js
#include utility.js
#include mami/settings.js
var AmiChat = function(chat, title, parent) {
var container = $e({ attrs: { id: 'chat' } });
var pub = {
getElement: function() { return container; },
appendTo: function(parent) { parent.appendChild(container); },
appendChild: function(child) {
if(typeof 'getElement' in child)
child = child.getElement();
container.appendChild(child);
},
};
pub.title = new AmiChatTitle(pub, title);
var copyright = new AmiCopyright(pub);
pub.copyright = copyright;
copyright.addLine([ ['Sock Chat', '//railgun.sh/sockchat'], ' © ', ['aroltd.com', 'http://aroltd.com'] ]);
copyright.addLine([ 'Ami © ', ['flash.moe', '//flash.moe'] ]);
var statusIndicator = new AmiStatusIndicator(pub);
pub.statusIndicator = statusIndicator;
var optionIcons = new AmiOptionIcons(pub);
pub.optionIcons = optionIcons;
var emoticonList = new AmiEmoticonList(pub);
pub.emoticonList = emoticonList;
var messageList = new AmiMessageList(pub);
pub.messageList = messageList;
var optionButtons = new AmiOptionButtons(pub);
pub.optionButtons = optionButtons;
chat.settings.watch('bbParse', function(value) {
optionButtons[value ? 'show' : 'hide']();
});
var sidebars = new AmiSidebars(pub);
pub.sidebars = sidebars;
sidebars.watch('show', function(sidebar) {
messageList.showSideBar(sidebar.isWide() ? 'wide' : 'thin');
});
sidebars.watch('hide', function() {
messageList.showSideBar('none');
});
var inputBox = new AmiInputBox(pub);
pub.inputBox = inputBox;
emoticonList.watch(function(emote) {
inputBox.insert(':' + emote.strings[0] + ':');
inputBox.focus();
});
var submitBox = new AmiSubmitBox(pub);
pub.submitBox = submitBox;
pub.colourPicker = new AmiColourPicker(pub);
if(parent !== undefined)
pub.appendTo(parent);
if(MamiSettings.isSupported())
optionIcons.create('export', 'Export modern client settings', 'export', () => MamiSettings.exportFile());
optionIcons.create('unembed', 'Unembed any embedded media', 'unembed', function() {
var buttons = $c('js-unembed-btn');
while(buttons.length > 0)
buttons[0].click();
});
optionIcons.create('uploads', 'Uploads', 'uploads', function() {
sidebars.toggle('uploadList');
});
optionIcons.create('help', 'Help', 'help', function() {
sidebars.toggle('helpList');
});
optionIcons.create('settings', 'Settings', 'settings', function() {
sidebars.toggle('settingsList');
});
optionIcons.create('users', 'Online users', 'users', function() {
sidebars.toggle('userList');
});
optionIcons.create('channels', 'Channels', 'channels', function() {
sidebars.toggle('chanList');
});
var muteToggle = optionIcons.create('audio', 'Mute sounds', 'sound', function() {
chat.settings.toggle('soundMute');
}, true);
chat.settings.watch('soundMute', function(value) {
muteToggle.setState(!value);
});
optionIcons.create('clear', 'Clear logs', 'clear', function() {
messageList.clear();
});
var scrollToggle = optionIcons.create('scroll', 'Auto scroll', 'autoscroll', function() {
chat.settings.toggle('msgAutoScroll');
}, true);
chat.settings.watch('msgAutoScroll', function(value) {
scrollToggle.setState(value);
});
submitBox.watch(function(ev) {
inputBox.enter(ev);
});
inputBox.watch('length', function(length) {
submitBox.setCurrentLength(length);
});
chat.sockChat.watch('session:start', function(info) {
inputBox.setMaxLength(info.ctx.maxMsgLength);
submitBox.setMaxLength(info.ctx.maxMsgLength);
});
chat.sockChat.watch('msg:remove', function(info) {
messageList.remove(info.msg.id);
});
chat.sockChat.watch('msg:clear', function() {
messageList.clear();
});
messageList.watch('nameInsert', function(msgInfo) {
inputBox.insert(msgInfo.sender.username);
inputBox.focus();
});
messageList.watch('delete', function(msgInfo) {
if(confirm(AmiStrings.getMenuString('delmsg')))
chat.sockChat.sendMessage('/delete ' + msgInfo.id);
});
return pub;
};

49
src/ami.js/colourpick.js Normal file
View file

@ -0,0 +1,49 @@
#include utility.js
var AmiColourPicker = function(parent) {
var container = $e({ attrs: { id: 'pickerNew', className: 'hidden' } }),
callback = undefined;
parent.appendChild(container);
var picker = new FwColourPicker(
function(picker, result) {
if(result === null)
return;
callback.call(picker, {
raw: result,
hex: FwColourPicker.hexFormat(result),
});
callback = undefined;
},
{
presets: futami.get('colours'),
},
null,
function() {
callback = undefined;
container.classList.add('hidden');
return true;
}
);
picker.appendTo(container);
var showAt = function(x, y, cb) {
if(typeof cb !== 'function')
throw 'missing callback';
callback = cb;
picker.setPosition(x, y);
container.classList.remove('hidden');
};
return {
show: function(ev, cb) {
var pos = typeof ev === 'object' ? picker.suggestPosition(ev) : { x: 10, y: 10 };
showAt(pos.x, pos.y, cb);
},
showAt: showAt,
hide: function() {
picker.close();
},
};
};

48
src/ami.js/common.js Normal file
View file

@ -0,0 +1,48 @@
var FutamiCommon = function(vars) {
vars = vars || {};
var get = function(name, fallback) {
return vars[name] || fallback || null;
};
return {
get: get,
getJson: function(name, onload, onerror, noCache) {
if(typeof onload !== 'function')
throw 'onload must be specified';
var xhr = new XMLHttpRequest;
xhr.onload = function() {
onload(JSON.parse(xhr.responseText));
};
if(typeof onerror === 'function')
xhr.onerror = function() { onerror(); };
xhr.open('GET', get(name));
if(noCache)
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.send();
},
};
};
FutamiCommon.load = function(callback, url) {
if(typeof callback !== 'function')
throw 'there must be a callback';
if(typeof url !== 'string' && 'FUTAMI_URL' in window)
url = window.FUTAMI_URL + '?t=' + Date.now().toString();
var xhr = new XMLHttpRequest;
xhr.onload = function() {
try {
callback(new FutamiCommon(JSON.parse(xhr.responseText)));
} catch(ex) {
callback(false);
}
};
xhr.onerror = function() {
callback(false);
};
xhr.open('GET', url);
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.send();
};

106
src/ami.js/compat.js Normal file
View file

@ -0,0 +1,106 @@
#include utility.js
if(!('WebSocket' in window) && 'MozWebSocket' in window)
window.WebSocket = window.MozWebSocket;
if(!('Map' in window))
window.Map = function(iterable) {
var keys = [], values = [];
var obj = { size: 0 };
if(typeof iterable === 'object')
if(Array.isArray(iterable)) {
for(var i in iterable) {
keys.push(iterable[i][0]);
values.push(iterable[i][1]);
++obj.size;
}
} else if('next' in iterable) {
for(;;) {
var state = iterable.next();
if(state.done) break;
keys.push(state.value[0]);
values.push(state.value[1]);
++obj.size;
}
}
obj.clear = function() {
keys = [];
values = [];
obj.size = 0;
};
obj.delete = function(key) {
var index = keys.indexOf(key);
if(key < 0) return false;
$ar(keys, index);
$ar(values, index);
--obj.size;
};
obj.entries = function() {
var index = -1;
return {
next: function() {
if(++index >= obj.size)
return { done: true };
return { value: [keys[index], values[index]] };
},
};
};
obj.forEach = function(callbackFn, thisArg) {
if(typeof callbackFn !== 'function') return;
if(thisArg === undefined) thisArg = obj;
for(var i = 0; i < obj.size; ++i)
callbackFn.call(thisArg, values[i], keys[i], obj);
};
obj.get = function(key) {
var index = keys.indexOf(key);
if(index < 0) return undefined;
return values[index];
};
obj.has = function(key) {
return keys.indexOf(key) >= 0;
};
obj.keys = function() {
var index = -1;
return {
next: function() {
if(++index >= obj.size)
return { done: true };
return { value: values[index] };
},
};
};
obj.set = function(key, value) {
var index = keys.indexOf(key);
if(index < 0) {
keys.push(key);
values.push(value);
++obj.size;
} else values[index] = value;
return obj;
};
obj.values = function() {
var index = -1;
return {
next: function() {
if(++index >= obj.size)
return { done: true };
return { value: keys[index] };
},
};
};
return obj;
};

38
src/ami.js/cookies.js Normal file
View file

@ -0,0 +1,38 @@
var AmiCookies = (function() {
var removeCookie = function(name) {
document.cookie = encodeURIComponent((name || '').toString()) + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
};
var getCookie = function(name) {
name = encodeURIComponent((name || '').toString());
var cookies = document.cookie.split(';'),
cookiesLength = cookies.length;
for(var i = 0; i < cookiesLength; ++i) {
var parts = cookies[i].trim().split('=', 2);
if(parts[0] === name)
return decodeURIComponent(parts[1]);
}
return undefined;
};
var setCookie = function(name, value) {
name = encodeURIComponent((name || '').toString());
value = encodeURIComponent((value || '').toString());
if(getCookie(name))
removeCookie(name);
var life = new Date();
life.setFullYear(life.getFullYear() + 1);
document.cookie = name + '=' + value + '; Path=/; Expires=' + life.toUTCString() + '; SameSite=Lax';
};
return {
getCookie: getCookie,
setCookie: setCookie,
removeCookie: removeCookie,
};
})();

32
src/ami.js/copyright.js Normal file
View file

@ -0,0 +1,32 @@
#include utility.js
var AmiCopyright = function(parent) {
var container = $e({ attrs: { id: 'copyright' } });
parent.appendChild(container);
return {
addLine: function(line) {
var children = [];
for(var i in line) {
var section = line[i];
if(typeof section === 'string')
children.push(section);
else if(Array.isArray(section))
children.push({
tag: 'a',
attrs: {
href: section[1],
target: '_blank',
},
child: section[0],
});
}
container.appendChild($e({
tag: 'p',
child: children,
}))
},
};
};

438
src/ami.js/ctx.js Normal file
View file

@ -0,0 +1,438 @@
#include chat.js
#include cookies.js
#include copyright.js
#include emotes.js
#include messages.js
#include notify.js
#include reconnect.js
#include servers.js
#include settings.js
#include sockchat.js
#include sound.js
#include styles.js
#include title.js
#include txtrigs.js
#include utility.js
var AmiContext = function(title, auth, loading) {
var pub = {};
var settings = new AmiSettings;
pub.settings = settings;
settings.define('style', 'string', '');
settings.define('soundMute', 'boolean');
settings.define('soundVolume', 'number', 0.8);
settings.define('soundPack', 'string');
settings.define('enableNotifs', 'boolean');
settings.define('msgAutoScroll', 'boolean', true, true, true);
settings.define('runJokeTriggers', 'boolean', true);
settings.define('dumpPackets', 'boolean');
settings.define('parseEmoticons', 'boolean', true);
settings.define('parseLinks', 'boolean', true);
settings.define('bbAutoEmbedV1', 'boolean', false);
settings.define('bbParse', 'boolean', true);
settings.define('bbParseColour', 'boolean', true);
settings.define('bbParseImage', 'boolean', true);
settings.define('bbParseVideo', 'boolean', true);
settings.define('bbParseAudio', 'boolean', true);
settings.define('bbPersistBold', 'boolean');
settings.define('bbPersistItalics', 'boolean');
settings.define('bbPersistUnderline', 'boolean');
settings.define('bbPersistStrike', 'boolean');
settings.define('bbPersistSjis', 'boolean');
settings.define('bbPersistColour', 'string');
settings.define('tmpSkipDomainPopUpThing', 'boolean', false);
settings.define('migrated', 'boolean', false);
if(!settings.get('migrated')) {
settings.set('migrated', true);
var scBBEnable = JSON.parse(AmiCookies.getCookie('sockchat_bbenable') || null),
scOpts = JSON.parse(AmiCookies.getCookie('sockchat_opts') || null),
scPersist = JSON.parse(AmiCookies.getCookie('sockchat_persist') || null),
scStyle = AmiCookies.getCookie('sockchat_style');
if(typeof scStyle === 'string')
settings.set('style', scStyle);
if(scOpts) {
if(typeof scOpts.bbcode === 'boolean')
settings.set('bbParse', scOpts.bbcode);
if(typeof scOpts.emotes === 'boolean')
settings.set('parseEmoticons', scOpts.emotes);
if(typeof scOpts.links === 'boolean')
settings.set('parseLinks', scOpts.links);
if(typeof scOpts.sound === 'boolean')
settings.set('soundMute', !scOpts.sound);
if(typeof scOpts.spack === 'string')
settings.set('soundPack', scOpts.spack);
if(typeof scOpts.volume === 'string')
settings.set('soundVolume', parseFloat(scOpts.volume));
else if(typeof scOpts.volume === 'number')
settings.set('soundVolume', scOpts.volume);
}
if(scBBEnable) {
if(typeof scBBEnable.color === 'boolean')
settings.set('bbParseColour', scBBEnable.color);
if(typeof scBBEnable.img === 'boolean')
settings.set('bbParseImage', scBBEnable.img);
if(typeof scBBEnable.video === 'boolean')
settings.set('bbParseVideo', scBBEnable.video);
if(typeof scBBEnable.audio === 'boolean')
settings.set('bbParseAudio', scBBEnable.audio);
}
if(scPersist) {
if(typeof scPersist.b === 'object')
settings.set('bbPersistBold', !!scPersist.b.enable);
if(typeof scPersist.i === 'object')
settings.set('bbPersistItalics', !!scPersist.i.enable);
if(typeof scPersist.u === 'object')
settings.set('bbPersistUnderline', !!scPersist.u.enable);
if(typeof scPersist.s === 'object')
settings.set('bbPersistStrike', !!scPersist.s.enable);
if(typeof scPersist.color === 'object')
settings.set('bbPersistColour', scPersist.color.enable ? (scPersist.color.value || '').toString() : null);
}
AmiCookies.removeCookie('sockchat_bbenable');
AmiCookies.removeCookie('sockchat_opts');
AmiCookies.removeCookie('sockchat_persist');
AmiCookies.removeCookie('sockchat_style');
}
var sound = new AmiSound;
pub.sound = sound;
settings.watch('soundVolume', function(value) {
sound.setVolume(value);
});
settings.watch('soundPack', function(value) {
sound.setPackName(value);
});
var textTriggers = new AmiTextTriggers;
pub.textTriggers = textTriggers;
settings.watch('runJokeTriggers', function(value) {
if(!textTriggers.hasTriggers())
futami.getJson('texttriggers', function(trigInfos) {
textTriggers.addTriggers(trigInfos);
});
});
var styles = new AmiStyles;
pub.styles = styles;
styles.register('beige', 'Beige');
styles.register('black', 'Black', true);
styles.register('blue', 'Blue');
styles.register('cobalt', 'Cobalt');
styles.register('halext', 'Halext');
styles.register('legacy', 'Legacy');
styles.register('lithium', 'Lithium');
styles.register('mio', 'Mio');
styles.register('misuzu', 'Misuzu');
styles.register('nico', 'Nico');
styles.register('oxygen', 'Oxygen');
styles.register('sulfur', 'Sulfur');
styles.register('techno', 'Techno');
styles.register('white', 'White');
styles.register('yuuno', 'Yuuno');
settings.watch('style', function(value) {
try {
styles.apply(value);
} catch(ex) {
styles.setDefault();
}
});
var notifications = new AmiNotify;
pub.notifications = notifications;
settings.watch('enableNotifs', function(value) {
if(!value) {
notifications.disable();
return;
}
notifications.enable(function(state) {
if(!state) settings.set('enableNotifs', false);
});
});
var emoticons = new AmiEmoticons;
pub.emoticons = emoticons;
pub.servers = new AmiServers;
pub.windowTitle = new AmiWindowTitle(title);
var sockChat = new AmiSockChat(auth);
pub.sockChat = sockChat;
var chat = new AmiChat(pub, title, document.body);
pub.chat = chat;
settings.watch('parseEmoticons', function(value) {
if(!value) {
chat.emoticonList.hide();
emoticons.clear();
return;
}
futami.getJson('emotes', function(emotes) {
if(Array.isArray(emotes))
emoticons.loadLegacy(emotes);
chat.emoticonList.render(emoticons);
});
});
var reconnecter = new AmiReconnecter(chat);
pub.reconnecter = reconnecter;
pub.pageIsHidden = function() {
if('visibilityState' in document)
return document.visibilityState !== 'visible';
if('hidden' in document)
return document.hidden;
return false;
};
sockChat.watch('conn:init', function(info) {
console.log('conn:init', info);
if(!info.wasConnected)
loading.setTextRaw(AmiStrings.getMenuString('conn'));
});
sockChat.watch('conn:ready', function(info) {
console.log('conn:ready', info);
if(!info.wasConnected)
loading.setTextRaw(AmiStrings.getMenuString('auth'));
chat.statusIndicator.setGreen('Ready!');
if(reconnecter.isActive())
reconnecter.session(function(man) { man.success(); });
});
sockChat.watch('conn:lost', function(info) {
console.log('conn:lost', info);
if(info.wasConnected) {
chat.statusIndicator.setRed('Connection with server has been lost.');
if(!reconnecter.isActive())
reconnecter.start(function() { sockChat.open(); });
UI.AddMessage('rc', null, UI.ChatBot, info.msg, false, false);
} else {
var msg = {
'_1000': 'The connection has been ended.',
'_1001': 'Something went wrong on the server side.',
'_1002': 'Your client sent broken data to the server.',
'_1003': 'Your client sent data to the server that it doesn\'t understand.',
'_1005': 'No additional information was provided.',
'_1006': 'You lost connection unexpectedly!',
'_1007': 'Your client sent broken data to the server.',
'_1008': 'Your client did something the server did not agree with.',
'_1009': 'Your client sent too much data to the server at once.',
'_1011': 'Something went wrong on the server side.',
'_1012': 'The server is restarting, reconnecting soon...',
'_1013': 'You cannot connect to the server right now, try again later.',
'_1015': 'Your client and the server could not establish a secure connection.',
}['_' + info.code.toString()] || ('Something caused an unexpected connection loss, the error code was: ' + info.code.toString() + '.');
loading.hideIndicator();
loading.setTextRaw(AmiStrings.getMenuString('term') + '<br/><br/>' + msg);
}
});
sockChat.watch('conn:error', function() {
console.log('conn:error');
if(reconnecter.isActive())
reconnecter.session(function(man) { man.fail(); });
});
sockChat.watch('ping:send', function() {
console.log('ping:send');
chat.statusIndicator.setYellow('Pinging...');
});
sockChat.watch('ping:long', function() {
console.log('ping:long');
chat.statusIndicator.setRed('Server is taking longer than 2 seconds to respond.');
});
sockChat.watch('ping:recv', function(info) {
console.log('ping:recv', info);
chat.statusIndicator.setGreen('Server took ' + info.diff.toLocaleString() + 'ms to respond.');
});
sockChat.watch('session:start', function(info) {
console.log('session:start', info);
if(!info.wasConnected) {
loading.hideIndicator();
loading.setTextRaw('Welcome!');
loading.close();
loading = undefined;
}
UserContext.self = new User(info.user.id, info.user.name, info.user.colour, info.user.permsRaw);
UserContext.self.channel = info.channel.name;
UI.AddUser(UserContext.self, false);
UI.RedrawUserList();
});
sockChat.watch('session:fail', function(info) {
console.log('session:fail', info);
var overlay = AmiStrings.getMenuString(info.session.reason);
if(info.baka) {
if(info.baka.perma)
overlay += '<br/>' + AmiStrings.getMenuString('eot');
else
overlay += '<br/>' + info.baka.until.toLocaleString();
}
overlay += '<br/><br/><a href="' + futami.get('login') + '?legacy=1">' + AmiStrings.getMenuString('back') + '</a>';
loading.hideIndicator();
loading.setTextRaw(overlay);
});
sockChat.watch('session:term', function(info) {
console.log('session:term', info);
var overlay = AmiStrings.getMenuString(info.baka.type);
if(info.baka.until)
overlay += '<br/>' + info.baka.until.toLocaleString();
overlay += '<br/><br/><a href="' + futami.get('login') + '?legacy=1">' + AmiStrings.getMenuString('back') + '</a>';
if(loading === undefined)
loading = new AmiLoadingOverlay(document.body);
loading.setTextRaw(overlay);
});
sockChat.watch('user:add', function(info) {
console.log('user:add', info);
if(UserContext.self.id !== info.user.id)
UI.AddUser(new User(info.user.id, info.user.name, info.user.colour, info.user.permsRaw, info.user.hidden));
if(info.msg) {
UI.AddMessage(info.msg.id, info.msg.time, UI.ChatBot, info.msg.text, true, false);
sound.playEventSound('join');
}
});
sockChat.watch('user:remove', function(info) {
console.log('user:remove', info);
if(info.msg) {
var sounds = {
'flood': ['flood', 'kick', 'leave'],
'kick': ['kick', 'leave'],
'timeout': ['timeout', 'leave'],
};
UI.AddMessage(info.msg.id, info.msg.time, UI.ChatBot, info.msg.text, true, false);
ami.sound.playEventSound(info.leave.type in sounds ? sounds[info.leave.type] : 'leave');
}
UI.RemoveUser(info.user.id);
});
sockChat.watch('user:update', function(info) {
console.log('user:update', info);
if(UserContext.self.id === info.user.id) {
UserContext.self.username = info.user.name;
UserContext.self.color = info.user.colour;
UserContext.self.permstr = info.user.permsRaw;
UserContext.self.EvaluatePermString();
UI.ModifyUser(UserContext.self);
} else {
UserContext.users[info.user.id].username = info.user.name;
UserContext.users[info.user.id].color = info.user.colour;
UserContext.users[info.user.id].permstr = info.user.permsRaw;
UserContext.users[info.user.id].EvaluatePermString();
UI.ModifyUser(UserContext.users[info.user.id]);
}
});
sockChat.watch('user:clear', function() {
console.log('user:clear');
UserContext.users = {};
UI.RedrawUserList();
});
sockChat.watch('chan:add', function(info) {
console.log('chan:add', info);
UI.AddChannel(info.channel.name, info.channel.hasPassword, info.channel.isTemporary, false);
});
sockChat.watch('chan:remove', function(info) {
console.log('chan:remove', info);
UI.RemoveChannel(info.channel.name);
});
sockChat.watch('chan:update', function(info) {
console.log('chan:update', info);
UI.ModifyChannel(info.channel.previousName, info.channel.name, info.channel.hasPassword, info.channel.isTemporary);
});
sockChat.watch('chan:clear', function() {
console.log('chan:clear');
UI.RedrawChannelList();
});
sockChat.watch('chan:focus', function(info) {
console.log('chan:focus', info);
UI.SwitchChannel(info.channel.name);
});
sockChat.watch('chan:join', function(info) {
console.log('chan:join', info);
if(UserContext.self.id !== info.user.id) {
UI.AddUser(new User(info.user.id, info.user.name, info.user.colour, info.user.permsRaw));
UI.AddMessage(info.msg.id, null, UI.ChatBot, info.msg.text, true, false);
ami.sound.playEventSound('join');
}
});
sockChat.watch('chan:leave', function(info) {
console.log('chan:leave', info);
if(UserContext.self.id !== info.user.id) {
UI.AddMessage(info.msg.id, null, UI.ChatBot, info.msg.text, true, false);
UI.RemoveUser(info.user.id);
ami.sound.playEventSound('leave');
}
});
sockChat.watch('msg:add', function(info) {
console.log('msg:add', info);
if(info.msg.silent !== undefined)
UI.AddMessage(info.msg.id, info.msg.time, info.msg.isBot ? UI.ChatBot : new User(info.msg.sender.id, info.msg.sender.name, info.msg.sender.colour, info.msg.sender.permsRaw), info.msg.text, !info.msg.silent, !info.msg.silent, info.msg.flagsRaw);
else if(info.msg.isBot)
UI.AddMessage(info.msg.id, info.msg.time, UI.ChatBot, info.msg.text, true, true, info.msg.flagsRaw);
else if(UserContext.self.id === info.msg.sender.id)
UI.AddMessage(info.msg.id, info.msg.time, UserContext.self, info.msg.text, true, true, info.msg.flagsRaw);
else
UI.AddMessage(info.msg.id, info.msg.time, UserContext.users[info.msg.sender.id], info.msg.text, true, true, info.msg.flagsRaw);
});
sockChat.watch('msg:remove', function(info) {
console.log('msg:remove', info);
});
sockChat.watch('msg:clear', function() {
console.log('msg:clear');
});
return pub;
};

33
src/ami.js/eeprom.js Normal file
View file

@ -0,0 +1,33 @@
var AmiEEPROM = function() {
//
};
AmiEEPROM.init = (function() {
var initialised = false;
return function(callback) {
if(initialised) {
if(callback)
callback(true);
return;
}
initialised = true;
// cuts off "/uploads", this is little disgusting
var eepromScript = futami.get('eeprom').slice(0, -8) + '/eeprom.js';
var script = document.createElement('script');
script.onload = function() {
if(callback)
callback(true);
};
script.onerror = function() {
console.error('Failed to load EEPROM script!');
if(callback)
callback(false);
};
script.charset = 'utf-8';
script.type = 'text/javascript';
script.src = eepromScript;
document.body.appendChild(script);
};
})();

110
src/ami.js/emotes.js Normal file
View file

@ -0,0 +1,110 @@
#include watcher.js
var AmiEmoticonList = function(parent) {
var container = $e({ attrs: { id: 'emotes' } }),
watcher = new AmiWatcher;
parent.appendChild(container);
var clear = function() {
$rc(container);
};
var add = function(emote) {
container.appendChild($e({
tag: 'img',
attrs: {
src: emote.url,
alt: emote.strings[0],
title: emote.strings[0],
onclick: function() { watcher.call(emote, [emote]); },
},
}));
};
var hide = function() { container.classList.add('hidden'); };
var show = function() { container.classList.remove('hidden'); };
return {
clear: clear,
hide: hide,
show: show,
add: add,
render: function(emotes, minRank) {
clear();
if(emotes.any(minRank)) {
emotes.forEach(minRank, add);
show();
} else hide();
},
watch: watcher.watch,
unwatch: watcher.unwatch,
};
};
var AmiEmoticons = function() {
var emotes = [];
var clear = function() {
emotes = [];
};
var add = function(emote) {
emotes.push(emote);
};
var addLegacy = function(emoteOld) {
var emote = {
url: emoteOld.Image.replace('https:', ''),
minRank: emoteOld.Hierarchy,
strings: [],
};
for(var i = 0; i < emoteOld.Text.length; ++i)
emote.strings.push(emoteOld.Text[i].slice(1, -1));
add(emote);
};
return {
clear: clear,
add: add,
addLegacy: addLegacy,
load: function(batch) {
for(var i in batch)
add(batch[i]);
},
loadLegacy: function(batch) {
for(var i in batch)
addLegacy(batch[i]);
},
forEach: function(minRank, callback) {
if(minRank === undefined) minRank = 0;
for(var i in emotes) {
var emote = emotes[i];
if(emote.minRank <= minRank)
callback(emote);
}
},
any: function(minRank) {
if(minRank === undefined) minRank = 0;
for(var i in emotes)
if(emotes[i].minRank <= minRank)
return true;
return false;
},
findByName: function(minRank, name, returnString) {
var found = [];
for(var i in emotes) {
var emote = emotes[i];
if(emote.minRank <= minRank) {
for(var j in emote.strings) {
var string = emote.strings[j];
if(string.indexOf(name) === 0) {
found.push(returnString ? string : emote);
break;
}
}
}
}
return found;
},
};
};

178
src/ami.js/inputbox.js Normal file
View file

@ -0,0 +1,178 @@
#include utility.js
#include watcher.js
var AmiInputBox = function(parent) {
var watchers = new AmiWatcherCollection,
input = $e({
tag: 'textarea',
attrs: {
cols: '2',
id: 'message',
autofocus: 'autofocus',
maxlength: '0',
},
}),
container = $e({
attrs: { id: 'inputFieldContainer' },
child: input,
});
parent.appendChild(container);
watchers.define(['tab', 'enter', 'paste', 'length']);
var getValue = function() { return input.value; };
var getStartPos = function() { return input.selectionStart; };
var setStartPos = function(pos) { input.selectionStart = pos; };
var getEndPos = function() { return input.selectionEnd; };
var setEndPos = function(pos) { input.selectionEnd = pos; };
var reportLength = function() {
watchers.call('length', pub, [input.value.length]);
};
var setValue = function(text) {
input.value = (text || '').toString();
reportLength();
};
var clear = function() {
input.value = '';
reportLength();
};
var pub = {
getValue: getValue,
setValue: setValue,
clear: clear,
getStartPos: getStartPos,
setStartPos: setStartPos,
getEndPos: getEndPos,
setEndPos: setEndPos,
isEmpty: function() { input.value.length < 1; },
focus: function() { input.focus(); },
enter: function(ev) {
watchers.call('enter', pub, [ev]);
},
watch: function(name, watcher) {
watchers.watch(name, watcher);
},
unwatch: function(name, watcher) {
watchers.unwatch(name, watcher);
},
setMaxLength: function(length) {
input.maxLength = length;
},
getWordAtCursor: function() {
var text = getValue(),
start = input.selectionStart,
position = start,
word = '';
while(position >= 0 && text.charAt(position - 1) !== ' ' && text.charAt(position - 1) !== "\n") {
--position;
word = text.charAt(position) + word;
}
return { start: start, word: word };
},
insert: function(text) {
text = (text || '').toString();
var value = getValue(),
start = getStartPos(),
end = getEndPos();
setValue(value.substring(0, start) + text + value.substring(end));
setStartPos(start + text.length);
setEndPos(start + text.length);
},
insertAt: function(location, text, strip) {
if(location < 0)
throw 'invalid location';
text = (text || '').toString();
strip = parseInt(strip);
var value = getValue(),
start = value.substring(0, location),
end = value.substring(location);
if(strip < 0)
start = start.slice(0, strip);
else if(strip > 0)
end = end.slice(strip);
setValue(start + text + end);
var cursor = start.length + text.length;
setStartPos(cursor);
setEndPos(cursor);
},
insertAround: function(before, after) {
before = (before || '').toString();
after = (after || '').toString();
var value = getValue(),
start = getStartPos(),
end = getEndPos();
setValue(value.substring(0, start) + before + value.substring(start, end) + after + value.substring(end));
setStartPos(start + before.length);
setEndPos(end + before.length);
},
setClassName: function(className, enabled) {
input.classList[enabled ? 'add' : 'remove'](className);
},
setStyleValue: function(name, value, enabled, single) {
if(enabled) {
if(single) {
input.style[name] = value;
} else {
var existingStyle = input.style[name];
if(existingStyle) {
var split = existingStyle.split(' ');
if(split.indexOf(value) < 0)
input.style[name] += ' ' + value;
} else
input.style[name] = value;
}
} else {
if(single) {
input.style[name] = null;
} else {
var existingStyle = input.style[name];
if(existingStyle) {
var split = existingStyle.split(' ');
if(split.length === 1) {
if(split[0] === value)
input.style[name] = null;
} else if(split.length > 1) {
$ari(split, value);
input.style[name] = split.join(' ');
}
}
}
}
},
};
input.addEventListener('keydown', function(ev) {
var key = 'key' in ev ? ev.key : ('which' in ev ? ev.which : ev.keyCode);
if((key === 'Enter' || key === 13) && !ev.shiftKey && !ev.ctrlKey) {
watchers.call('enter', pub, [ev]);
return;
}
if((key === 'Tab' || key === 9) && !ev.shiftKey && !ev.ctrlKey) {
watchers.call('tab', pub, [ev]);
return;
}
});
input.addEventListener('input', function() {
reportLength();
});
input.addEventListener('paste', function(ev) {
watchers.call('paste', pub, [ev]);
});
return pub;
};

44
src/ami.js/loadoverlay.js Normal file
View file

@ -0,0 +1,44 @@
#include utility.js
var AmiLoadingOverlay = function(parent, indicatorShown) {
var text = $e({
attrs: { id: 'conntxt' },
child: 'Loading Sock Chat...',
});
var indicator = $e({ attrs: { id: 'indicator' } });
var container = $e({
attrs: { id: 'connmsg' },
child: {
attrs: { id: 'info' },
child: [text, indicator],
},
});
var appendTo = function(parent) { parent.appendChild(container); };
var destroy = function() { $r(container); };
var showIndicator = function() { indicator.classList.remove('hidden'); };
var hideIndicator = function() { indicator.classList.add('hidden'); };
var toggleIndicator = function(visible) { indicator.classList[visible ? 'remove' : 'add']('hidden') };
toggleIndicator(indicatorShown);
if(parent !== undefined)
appendTo(parent);
return {
close: function() {
container.classList.add('connmsg-exit');
setTimeout(destroy, 1000);
},
getElement: function() { return container; },
appendTo: appendTo,
showIndicator: showIndicator,
hideIndicator: hideIndicator,
toggleIndicator: toggleIndicator,
setTextRaw: function(raw) { text.innerHTML = raw; },
destroy: destroy,
};
};

61
src/ami.js/main.js Normal file
View file

@ -0,0 +1,61 @@
#buildvars
#include compat.js
#include common.js
#include ctx.js
#include loadoverlay.js
#include mszauth.js
#include ts_chat.js
#include mami/domaintrans.jsx
#include mami/settings.js
(function() {
var loading = new AmiLoadingOverlay(document.body, true);
FutamiCommon.load(function(futami) {
if(typeof futami !== 'object') {
alert('Failed to load environment settings!');
return;
}
window.futami = futami;
var auth = new AmiMisuzuAuth(futami.get('token'));
var refreshInfo = function(next) {
auth.refresh(function(token) {
if(token.ok === false) {
location.assign(futami.get('login') + '?legacy=1');
return;
}
if(typeof next === 'function')
next(token.ok);
});
};
var ami = new AmiContext(futami.get('title'), auth, loading);
window.ami = ami;
const actuallyLoadChat = function() {
Chat.Main(auth);
ami.sockChat.open();
window.addEventListener('beforeunload', () => ami.sockChat.close());
};
setInterval(refreshInfo, 600000);
refreshInfo(function() {
if(!ami.settings.get('tmpSkipDomainPopUpThing')) {
const adt = AmiDomainTransition(
MamiSettings.isSupported() ? () => MamiSettings.exportFile() : undefined,
() => {
ami.settings.set('tmpSkipDomainPopUpThing', true);
adt.remove();
actuallyLoadChat();
}
);
adt.appendTo(document.body);
} else actuallyLoadChat();
});
});
})();

View file

@ -0,0 +1,69 @@
#include common.js
#include utility.js
const AmiDomainTransition = function(onExport, onDismiss) {
if(typeof onDismiss !== 'function')
throw 'onDismiss must be a function';
let exportTidbit, modernButton, exportButton;
let arrowsTarget1, arrowsTarget2;
const html = <div class="domaintrans">
<div class="domaintrans-body">
<div class="domaintrans-domain domaintrans-domain-main">
<div class="domaintrans-domain-header">
Compatibility Chat
</div>
<div class="domaintrans-domain-display">
<div class="domaintrans-domain-text domaintrans-domain-orig"><div>sockchat.flashii.net/legacy</div></div>
{arrowsTarget1 = <div class="domaintrans-domain-arrow" />}
<div class="domaintrans-domain-text domaintrans-domain-new"><div>sockchat.flashii.net</div></div>
</div>
</div>
<div class="domaintrans-domain domaintrans-domain-compat">
<div class="domaintrans-domain-header">
Flashii Chat
</div>
<div class="domaintrans-domain-display">
<div class="domaintrans-domain-text domaintrans-domain-orig"><div>sockchat.flashii.net</div></div>
{arrowsTarget2 = <div class="domaintrans-domain-arrow" />}
<div class="domaintrans-domain-text domaintrans-domain-new"><div>chat.flashii.net</div></div>
</div>
</div>
<div class="domaintrans-text">
<p>At long last, modern chat is being moved back to its proper subdomain. This means the original client can have the sockchat subdomain all to itself like the AJAX Chat!</p>
{exportTidbit = <p>You can use this screen to export your settings for the modern client. Pressing "Export settings" will let you save your settings for the modern client, you can then import this file using the "Import settings" button within the modern client.</p>}
<p>This screen won't show up again after you press "Continue to chat".</p>
</div>
<div class="domaintrans-options">
{modernButton = <div><a class="domaintrans-option" href={MAMI_URL} target="_blank">
<div class="domaintrans-option-icon"><div class="sprite sprite-autoscroll" /></div>
<div class="domaintrans-option-text">Open modern client</div>
</a></div>}
{exportButton = <div><button class="domaintrans-option" onclick={onExport}>
<div class="domaintrans-option-icon"><div class="sprite sprite-autoscroll" /></div>
<div class="domaintrans-option-text">Export settings</div>
</button></div>}
<div><button class="domaintrans-option" onclick={onDismiss}>
<div class="domaintrans-option-icon"><div class="sprite sprite-autoscroll" /></div>
<div class="domaintrans-option-text">Continue to chat</div>
</button></div>
</div>
</div>
</div>;
for(let i = 0; i < 5; ++i)
arrowsTarget1.appendChild(<span></span>);
for(let i = 0; i < 5; ++i)
arrowsTarget2.appendChild(<span></span>);
if(typeof onExport !== 'function') {
$r(exportTidbit);
$r(modernButton);
$r(exportButton);
}
return {
appendTo: parent => parent.appendChild(html),
remove: () => $r(html),
};
};

View file

@ -0,0 +1,80 @@
#include utility.js
const MamiSettings = (function() {
const isSupported = () => {
if(navigator.userAgent.indexOf('MSIE') >= 0)
return false;
if(navigator.userAgent.indexOf('Trident/') >= 0)
return false;
if(!('Blob' in window) || !('prototype' in window.Blob)
|| window.Blob.prototype.constructor.name !== 'Blob')
return false;
if(!('AudioContext' in window) && !('webkitAudioContext' in window))
return false;
if(!('URL' in window) || !('createObjectURL' in window.URL)
|| window.URL.prototype.constructor.name !== 'URL')
return false;
if(!('localStorage' in window))
return false;
try {
var testVar = 'test';
localStorage.setItem(testVar, testVar);
if(localStorage.getItem(testVar) !== testVar)
throw '';
localStorage.removeItem(testVar);
} catch(e) {
return false;
}
try {
eval('const c = 1; let l = 2;');
} catch(e) {
return false;
}
try {
eval('for(const i of ["a", "b"]);');
} catch(e) {
return false;
}
return true;
}
const exportFile = () => {
const data = { a: 'Mami Settings Export', v: 1, d: [] };
for(let i = 0; i < localStorage.length; ++i) {
const key = localStorage.key(i);
if(key.substring(0, 4) !== 'umi-')
continue;
data.d.push({
i: key.substring(4),
v: JSON.parse(localStorage.getItem(key)),
});
}
const exp = $e('a', {
href: URL.createObjectURL(new Blob(
[btoa(JSON.stringify(data))],
{ type: 'application/octet-stream' }
)),
download: 'settings.mami',
target: '_blank',
style: { display: 'none' }
});
document.body.appendChild(exp);
exp.click();
$r(exp);
};
return {
isSupported: isSupported,
exportFile: exportFile,
};
})();

137
src/ami.js/messages.js Normal file
View file

@ -0,0 +1,137 @@
#include utility.js
#include watcher.js
var AmiMessageList = function(parent) {
var container = $e({ attrs: { id: 'chatList', className: 'fullWidth' } }),
isEven = true,
idPfx = 'sock_msg_',
watchers = new AmiWatcherCollection;
parent.appendChild(container);
watchers.define(['nameInsert', 'delete']);
var reflow = function() {
isEven = true;
for(var i = 0; i < container.children.length; ++i) {
var child = container.children[i];
child.classList[isEven ? 'remove' : 'add']('rowOdd');
child.classList[isEven ? 'add' : 'remove']('rowEven');
isEven = !isEven;
}
};
return {
add: function(msgInfo) {
var nameStyle = { color: msgInfo.sender.color };
if(msgInfo.nameBold) nameStyle.fontWeight = 'bold';
if(msgInfo.nameItalics) nameStyle.fontStyle = 'italic';
if(msgInfo.nameUnderline) nameStyle.textDecoration = 'underline';
var nameBody = msgInfo.sender.username;
if(msgInfo.sender.isBot())
nameBody = {
tag: 'span',
attrs: { className: 'botName' },
child: nameBody,
};
var messageBody = [
{
tag: 'span',
attrs: { className: 'date' },
child: '(' + msgInfo.created.toLocaleTimeString() + ')',
},
' ',
{
tag: 'span',
attrs: {
style: nameStyle,
onclick: function() {
watchers.call('nameInsert', msgInfo, [msgInfo]);
},
},
child: nameBody,
},
{
tag: 'span',
attrs: { className: 'msgColon' },
child: msgInfo.showColon ? ':' : '',
},
' ',
{
tag: 'span',
attrs: { className: 'msgBreak' },
child: { tag: 'br' },
},
];
if(msgInfo.isPM) {
if(msgInfo.pmTargetName)
messageBody.push({
tag: 'i',
child: AmiStrings.getMenuString('whisperto', msgInfo.pmTargetName),
});
else
messageBody.push({
tag: 'i',
child: AmiStrings.getMenuString('whisper'),
});
messageBody.push(' ');
}
var message = $e({
attrs: {
id: idPfx + msgInfo.id,
className: isEven ? 'rowEven' : 'rowOdd'
},
child: messageBody,
});
message.insertAdjacentHTML('beforeend', msgInfo.bodyRaw);
if(msgInfo.canDelete)
message.appendChild($e({
tag: 'a',
attrs: {
title: 'Delete this message',
className: 'sprite-small sprite-delete',
style: {
float: 'right',
margin: '2px 0 0 0',
},
onclick: function() {
watchers.call('delete', msgInfo, [msgInfo]);
},
},
}));
isEven = !isEven;
container.appendChild(message);
return message;
},
remove: function(msgId) {
$ri(idPfx + msgId);
reflow();
},
clear: function() {
$rc(container);
isEven = true;
},
reflow: reflow,
showSideBar: function(type) {
container.classList[type === 'wide' ? 'add' : 'remove']('wideSideVisible');
container.classList[type === 'thin' ? 'add' : 'remove']('userListVisible');
container.classList[type === 'none' ? 'add' : 'remove']('fullWidth');
},
scrollToBottom: function() {
container.scrollTop = container.scrollHeight;
},
watch: function(name, watcher) {
watchers.watch(name, watcher);
},
unwatch: function(name, watcher) {
watchers.unwatch(name, watcher);
},
};
};

39
src/ami.js/mszauth.js Normal file
View file

@ -0,0 +1,39 @@
var AmiMisuzuAuth = function(refreshUrl) {
var userId = undefined,
authToken = undefined;
return {
getUserId: function() { return userId; },
getAuthToken: function() { return authToken; },
getInfo: function() {
return {
method: 'Misuzu',
token: authToken,
};
},
getHttpAuth: function() {
return 'Misuzu ' + authToken;
},
refresh: function(callback) {
var xhr = new XMLHttpRequest;
xhr.onload = function() {
if(xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
if(data.ok === true) {
userId = data.usr.toString();
authToken = data.tkn;
}
callback(data);
} else
callback({ok: null});
};
xhr.onerror = function() {
callback({ok: null});
};
xhr.open('GET', refreshUrl);
xhr.withCredentials = true;
xhr.send();
},
};
};

78
src/ami.js/notify.js Normal file
View file

@ -0,0 +1,78 @@
var AmiNotify = function() {
var enabled = false;
var enable = function(callback) {
if(typeof callback !== 'function')
callback = function() {};
if(enabled) {
callback(true);
return;
}
if('Notification' in window) {
if(Notification.permission === 'granted') {
callback(enabled = true);
return;
}
if(Notification.permission !== 'denied') {
Notification.requestPermission().then(function(perm) {
callback(enabled = perm === 'granted');
});
return;
}
}
callback(false);
};
var disable = function() {
enabled = false;
};
return {
enable: enable,
disable: disable,
display: function(info) {
if(!enabled)
return;
var title = info.title;
if(typeof title !== 'string')
throw 'missing title property';
var opts = {};
if(typeof info.text === 'string')
opts.body = info.text;
if(typeof info.data !== 'undefined')
opts.data = info.data;
if(typeof info.icon === 'string')
opts.icon = info.icon;
if(typeof info.tag === 'string')
opts.tag = info.tag;
if(typeof info.dir === 'string')
opts.dir = info.dir;
var notif = new Notification(title, opts);
if(typeof info.onclick === 'function')
notif.addEventListener('click', function(ev) {
info.onclick.call(notif, ev);
});
if(typeof info.onshow === 'function')
notif.addEventListener('show', function(ev) {
info.onshow.call(notif, ev);
});
if(typeof info.onerror === 'function')
notif.addEventListener('error', function(ev) {
info.onerror.call(notif, ev);
});
if(typeof info.onclose === 'function')
notif.addEventListener('close', function(ev) {
info.onclose.call(notif, ev);
});
},
};
};

67
src/ami.js/opticons.js Normal file
View file

@ -0,0 +1,67 @@
#include utility.js
var AmiOptionIcons = function(parent) {
var container = $e({ attrs: { id: 'optionsContainer' } }),
icons = new Map;
parent.appendChild(container);
var createIcon = function(name, title, icon, onclick, enabled) {
var pub = {};
var elem = $e({
tag: 'a',
attrs: {
href: 'javascript:void(0);',
title: title,
className: 'optIcon sprite sprite-' + icon,
onclick: function(ev) { performClick(ev); },
},
});
var hasState = typeof enabled === 'boolean';
var setState = function(state) {
if(!hasState) return;
elem.classList[state ? 'add' : 'remove']('on');
elem.classList[state ? 'remove' : 'add']('off');
};
if(hasState)
setState(enabled);
var performClick = function(ev) {
onclick.call(pub, pub, ev);
};
pub.getName = function() { return name; };
pub.getTitle = function() { return title; };
pub.getElement = function() { return elem; };
pub.setState = setState;
pub.performClick = performClick;
return pub;
};
return {
create: function(name, title, icon, onclick, enabled) {
if(icons.has(name))
throw 'an icon with this name already exists';
var iconInfo = createIcon(name, title, icon, onclick, enabled);
icons.set(iconInfo.getName(), iconInfo);
container.appendChild(iconInfo.getElement());
return iconInfo;
},
get: function(name) {
return icons.get(name);
},
remove: function(name) {
if(!icons.has(name))
return;
var iconInfo = icons.get(name);
container.removeChild(iconInfo.getElement());
icons.delete(name);
},
clear: function() {
icons.clear();
$rc(container);
},
};
};

220
src/ami.js/reconnect.js Normal file
View file

@ -0,0 +1,220 @@
#include utility.js
var AmiReconnecter = function(parent) {
var handler = new AmiReconnectHandler,
indicator = new AmiReconnectIndicator(parent, handler);
return {
isActive: function() {
return handler.isActive();
},
start: function(idleCallback, successCallback, failCallback) {
if(typeof idleCallback !== 'function')
throw 'idleCallback must be a function';
handler.start(
idleCallback,
function(fails) {
indicator.hide();
if(typeof successCallback === 'function')
successCallback(fails);
},
function(fails) {
if(fails > 0) indicator.showCounter();
if(typeof failCallback === 'function')
failCallback(fails);
}
);
},
session: function(body) {
handler.session(body);
},
clear: function() {
handler.clear();
indicator.hide();
},
};
};
var AmiReconnectHandler = function() {
var fails = 0,
delays = undefined,
timeout = undefined;
var idleCallback = undefined,
successCallback = undefined,
failCallback = undefined;
var started = 0,
delay = 0;
var setDefaultDelays = function() {
delays = [0, 10000, 15000, 30000, 45000, 60000, 120000, 300000];
};
var setCustomDelays = function(customDelays) {
delays = customDelays.slice(0);
delays[0] = 0;
};
setDefaultDelays();
var isActive = function() {
return idleCallback !== undefined;
};
var clearAttemptTimeout = function() {
clearTimeout(timeout);
timeout = undefined;
};
var clear = function() {
clearAttemptTimeout();
fails = 0;
idleCallback = undefined;
successCallback = undefined;
failCallback = undefined;
started = 0;
delay = 0;
};
var manager = {
success: function() {
if(successCallback !== undefined)
successCallback(fails);
clear();
},
fail: function() {
++fails;
if(failCallback !== undefined)
failCallback(fails);
attempt();
},
};
var attemptBody = function() {
clearAttemptTimeout();
idleCallback.call(manager, manager, fails, delay);
};
var attempt = function() {
started = Date.now();
delay = fails < delays.length ? delays[fails] : delays[delays.length - 1];
if(delay < 1) attemptBody();
else timeout = setTimeout(attemptBody, delay);
};
return {
isActive: isActive,
session: function(callback) {
callback.call(manager, manager);
},
start: function(idle, success, fail) {
if(typeof idle !== 'function' || isActive())
return;
clear();
idleCallback = idle;
successCallback = success;
failCallback = fail;
attempt();
},
clear: clear,
getTimeRemaining: function() {
return Math.max(0, (started + delay) - Date.now());
},
};
};
var AmiReconnectIndicator = function(parent, handler) {
var counterTime = $e({
tag: 'span',
attrs: {
id: 'reconnectCounterTime',
},
child: '0.0',
});
var counterBody = $e({
attrs: {
id: 'reconnectCounter',
className: 'hidden',
},
child: ['Reconnecting in ', counterTime, ' seconds.'],
});
var connectEllipsis = $e({
tag: 'span',
attrs: {
id: 'reconnectConnectEllipsis',
},
child: '...',
});
var connectBody = $e({
attrs: {
id: 'reconnectConnect',
className: 'hidden',
},
child: ['Attempting to reconnect', connectEllipsis],
});
var body = $e({
attrs: {
id: 'reconnectIndicator',
className: 'hidden',
},
child: [
{ child: 'WARNING: Connection Problem' },
counterBody,
connectBody,
],
});
parent.appendChild(body);
var counterInterval = undefined;
var clearCounterInterval = function() {
clearInterval(counterInterval);
counterInterval = undefined;
};
var setTime = function(time) {
counterTime.textContent = (parseFloat(time || 0) / 1000).toFixed(1);
};
var show = function() {
body.classList.remove('hidden');
};
var showCounter = function() {
clearCounterInterval();
connectBody.classList.add('hidden');
counterBody.classList.remove('hidden');
show();
counterInterval = setInterval(function() {
var remaining = handler.getTimeRemaining();
if(remaining < 100) showConnect();
else setTime(remaining);
}, 100);
};
var showConnect = function() {
clearCounterInterval();
counterBody.classList.add('hidden');
connectBody.classList.remove('hidden');
show();
};
return {
show: show,
hide: function() {
body.classList.add('hidden');
clearCounterInterval();
},
setTime: setTime,
showCounter: showCounter,
showConnect: showConnect,
};
};

22
src/ami.js/servers.js Normal file
View file

@ -0,0 +1,22 @@
#include common.js
#include utility.js
var AmiServers = function() {
var servers = futami.get('servers').slice(0),
index = 0xFFFF;
$as(servers);
return {
getServer: function(callback) {
if(++index >= servers.length)
index = 0;
var server = servers[index];
if(server.indexOf('//') >= 0)
server = location.protocol.replace('http', 'ws') + server;
callback(server);
},
};
};

231
src/ami.js/settings.js Normal file
View file

@ -0,0 +1,231 @@
#include cookies.js
#include utility.js
#include watcher.js
// really need to replace null with undefined where possible, in mami too
// wondering if there's room for the different backends here
var AmiSettings = function() {
var pub = {};
var prefix = 'ami_',
types = ['boolean', 'number', 'string'];
var settings = new Map,
locked = [];
var createSetting = function(name, type, defaultValue, mutable, virtual) {
var pub = {},
cookieName = prefix + name,
watchers = new AmiWatcher;
var virtualValue = undefined;
var getValue = function() {
if(!mutable)
return defaultValue;
var value = virtual ? virtualValue : AmiCookies.getCookie(cookieName);
if(value === undefined)
return defaultValue;
value = JSON.parse(value);
if(value === null)
return defaultValue;
return value;
};
var callWatchers = function() {
watchers.call(pub, getValue(), name);
};
var removeValue = function() {
if(locked.indexOf(name) >= 0)
return;
locked.push(name);
if(virtual) virtualValue = undefined;
else AmiCookies.removeCookie(cookieName);
callWatchers();
$ari(locked, name);
};
var setValue = function(value) {
if(value === undefined || value === null || value === defaultValue) {
removeValue();
return;
}
if(typeof value !== type)
throw 'value is not correct type';
if(locked.indexOf(name) >= 0)
return;
locked.push(name);
value = JSON.stringify(value);
if(virtual) virtualValue = value;
else AmiCookies.setCookie(cookieName, value);
callWatchers();
$ari(locked, name);
};
pub.getName = function() { return name; };
pub.getType = function() { return type; };
pub.getDefault = function() { return defaultValue; };
pub.isMutable = function() { return mutable; };
pub.isVirtual = function() { return virtual; };
pub.getValue = getValue;
pub.setValue = setValue;
pub.removeValue = removeValue;
pub.touch = callWatchers;
pub.hasValue = function() {
if(!mutable) return false;
var value = virtual ? virtualValue : AmiCookies.getCookie(cookieName);
if(value === undefined) return false;
value = JSON.parse(value);
if(value === null || value === defaultValue) return false;
return true;
};
pub.toggleValue = function() {
if(type !== 'boolean')
throw 'cannot toggle non-boolean setting';
if(locked.indexOf(name) >= 0)
return;
locked.push(name);
var value = virtual ? virtualValue : AmiCookies.getCookie(cookieName);
if(value === undefined)
value = defaultValue;
else
value = JSON.parse(virtual ? virtualValue : AmiCookies.getCookie(cookieName));
value = JSON.stringify(!value);
if(virtual) virtualValue = value;
else AmiCookies.setCookie(cookieName, value);
callWatchers();
$ari(locked, name);
};
pub.watch = function(watcher) {
if(typeof watcher !== 'function')
throw 'watcher must be a function';
watchers.watch(watcher, pub, getValue(), name);
};
pub.unwatch = function(watcher) {
watchers.unwatch(watcher);
};
pub.virtualise = function() {
if(!mutable || virtual)
return;
virtual = true;
virtualValue = AmiCookies.getCookie(cookieName);
};
return pub;
};
return {
has: function(name) {
var setting = settings.get(name);
if(setting === undefined) return false;
return setting.hasValue();
},
get: function(name) {
var setting = settings.get(name);
if(setting === undefined)
throw 'attempting to get value of undefined setting';
return setting.getValue();
},
getSetting: function() {
return settings.get(name);
},
set: function(name, value) {
var setting = settings.get(name);
if(setting === undefined)
throw 'attempting to set value of undefined setting';
setting.setValue(value);
},
remove: function(name) {
var setting = settings.get(name);
if(setting === undefined)
throw 'attempting to remove value of undefined setting';
setting.removeValue();
},
toggle: function(name) {
var setting = settings.get(name);
if(setting === undefined)
throw 'attempting to toggle undefined setting';
setting.toggleValue();
},
touch: function(name) {
var setting = settings.get(name);
if(setting === undefined)
throw 'attempting to touch undefined setting';
setting.touch();
},
watch: function(name, watcher) {
var setting = settings.get(name);
if(setting === undefined)
throw 'attempting to watch undefined setting';
setting.watch(watcher);
},
unwatch: function(name, watcher) {
var setting = settings.get(name);
if(setting === undefined)
throw 'attempting to unwatch undefined setting';
setting.unwatch(watcher);
},
virtualise: function(name) {
var setting = settings.get(name);
if(setting === undefined)
throw 'attempting to virtualise undefined setting';
setting.virtualise();
},
define: function(name, type, defaultValue, mutable, virtual) {
if(typeof name !== 'string')
throw 'name must be string';
name = name.trim();
if(types.indexOf(type) < 0)
throw 'invalid type specified';
if(mutable === undefined) mutable = true;
else mutable = !!mutable;
virtual = !!virtual;
if(defaultValue !== null) {
if(defaultValue === undefined)
defaultValue = null;
else if(type !== typeof defaultValue)
throw 'defaultValue type must match setting type or be null';
}
var setting = createSetting(name, type, defaultValue, mutable, virtual);
settings.set(name, setting);
return setting;
},
undefine: function(name) {
var setting = settings.get(name);
if(setting === undefined)
return;
settings.delete(name);
setting.removeValue();
},
forEach: function(body) {
settings.forEach(body);
},
};
};

107
src/ami.js/sidebars.js Normal file
View file

@ -0,0 +1,107 @@
#include utility.js
#include watcher.js
var AmiSidebars = function(container) {
var sidebars = new Map,
watchers = new AmiWatcherCollection,
active = undefined;
watchers.define(['show', 'hide']);
var createSidebar = function(sidebar) {
if(!('getName' in sidebar) || !('isWide' in sidebar) || !('getBody' in sidebar))
throw 'object does not contain expected functions';
var name = sidebar.getName(),
wide = sidebar.isWide(),
body = sidebar.getBody();
body.classList.add('body');
var classes = ['sidebar', 'hidden'];
if(wide) classes.push('widebar');
var titleElem = $e({ attrs: { className: 'top' } }),
elem = $e({
attrs: {
id: name,
classList: classes,
},
child: [titleElem, body],
});
var hide = function() {
elem.classList.add('hidden');
active = undefined;
};
var show = function() {
if(active !== undefined) {
if(active.getName() === name)
return;
active.hide();
}
active = sidebar;
elem.classList.remove('hidden');
};
sidebar.setTitle = function(title) { titleElem.textContent = (title || '').toString(); };
sidebar.isWide = function() { return elem.classList.contains('widebar'); };
sidebar.getElement = function() { return elem; };
sidebar.hide = hide;
sidebar.show = show;
if('apply' in sidebar)
sidebar.apply(sidebar);
if('reloadStrings' in sidebar)
sidebar.reloadStrings();
return sidebar;
};
var hide = function() {
if(active === undefined)
return;
watchers.call('hide', active, [active]);
active.hide();
};
var show = function(name) {
var sidebar = sidebars.get(name);
if(sidebar === undefined)
return;
sidebar.show();
watchers.call('show', sidebar, [sidebar]);
};
var toggle = function(name) {
if(active === undefined || active.getName() !== name)
show(name);
else
hide();
};
return {
create: function(name, wide, children) {
if(sidebars.has(name))
throw 'an icon with this name already exists';
var sidebar = createSidebar(name, wide, children);
sidebars.set(sidebar.getName(), sidebar);
container.appendChild(sidebar.getElement());
return sidebar;
},
has: function(name) {
return sidebars.has(name);
},
get: function(name) {
return sidebars.get(name);
},
hide: hide,
show: show,
toggle: toggle,
watch: function(name, watcher) {
watchers.watch(name, watcher);
},
unwatch: function(name, watcher) {
watchers.unwatch(name, watcher);
},
};
};

View file

@ -0,0 +1,75 @@
#include utility.js
var AmiChannelsSidebar = function() {
var sbName = 'chanList',
body = $e(),
activeChannel = undefined;
var setTitle;
var rowEven = false;
var createChannel = function(channelInfo) {
var toString = function() {
var str = '';
if(channelInfo.hasPassword())
str += '*';
if(channelInfo.isTemporary())
str += '[';
str += channelInfo.getName();
if(channelInfo.isTemporary())
str += ']';
return str;
};
var nameElem = $e({
attrs: { style: { display: 'black' } },
child: toString(),
}),
elem = $e({
attrs: {
className: (rowEven ? 'rowEven' : 'rowOdd'),
},
child: [nameElem],
});
rowEven = !rowEven;
return {
getElement: function() {
return elem;
},
setActive: function(state) {
elem.style.fontWeight = state ? 'bold' : null;
},
update: function() {
nameElem.textContent = toString();
},
};
};
return {
getName: function() { return sbName; },
isWide: function() { return false; },
getBody: function() { return body; },
apply: function(obj) {
setTitle = obj.setTitle;
},
reloadStrings: function() {
setTitle(AmiStrings.getMenuString('chans'));
},
add: function(channelInfo) {
//
},
update: function(name, channelInfo) {
//
},
remove: function(name) {
//
},
switch: function(name) {
//
},
};
};

View file

@ -0,0 +1,45 @@
#include strings.js
#include utility.js
var AmiHelpSidebar = function() {
var sbName = 'helpList',
list = $e({ tag: 'table' }),
body = $e({ child: list });
var setTitle;
var clear = function() {
while(list.lastChild)
list.removeChild(list.lastChild);
};
return {
getName: function() { return sbName; },
isWide: function() { return true; },
getBody: function() { return body; },
apply: function(obj) {
setTitle = obj.setTitle;
},
reloadStrings: function() {
setTitle(AmiStrings.getMenuString('help'));
clear();
var rowEven = false;
AmiStrings.forEachHelpString(function(helpInfo) {
var row = list.insertRow(0);
row.className = rowEven ? 'rowEven' : 'rowOdd';
var title = row.insertCell(0);
title.style.width = '50%';
title.textContent = helpInfo.title + ':';
var example = row.insertCell(1);
example.appendChild($e({ tag: 'i', child: helpInfo.format }));
rowEven = !rowEven;
});
},
clear: clear,
};
};

View file

@ -0,0 +1,20 @@
#include utility.js
var AmiSettingsSidebar = function() {
var sbName = 'settingsList',
body = $e({ child: { tag: 'table' } });
var setTitle;
return {
getName: function() { return sbName; },
isWide: function() { return true; },
getBody: function() { return body; },
apply: function(obj) {
setTitle = obj.setTitle;
},
reloadStrings: function() {
setTitle(AmiStrings.getMenuString('sets'));
},
};
};

View file

@ -0,0 +1,54 @@
#include utility.js
var AmiUploadsSidebar = function() {
var sbName = 'uploadList',
body = $e({
child: [
{
attrs: {
className: 'eepromForm',
},
child: [
{
tag: 'input',
attrs: {
id: 'uploadSelector',
type: 'file',
class: 'hidden',
multiple: true,
},
},
{
tag: 'input',
attrs: {
id: 'uploadButton',
type: 'button',
value: 'Select file',
},
},
],
},
{
tag: 'table',
attrs: {
id: 'uploadHistory',
class: 'eepromHistory',
},
},
],
});
var setTitle;
return {
getName: function() { return sbName; },
isWide: function() { return false; },
getBody: function() { return body; },
apply: function(obj) {
setTitle = obj.setTitle;
},
reloadStrings: function() {
setTitle(AmiStrings.getMenuString('uploads'));
},
};
};

View file

@ -0,0 +1,20 @@
#include utility.js
var AmiUsersSidebar = function() {
var sbName = 'userList',
body = $e();
var setTitle;
return {
getName: function() { return sbName; },
isWide: function() { return false; },
getBody: function() { return body; },
apply: function(obj) {
setTitle = obj.setTitle;
},
reloadStrings: function() {
setTitle(AmiStrings.getMenuString('online'));
},
};
};

522
src/ami.js/sockchat.js Normal file
View file

@ -0,0 +1,522 @@
#include servers.js
#include watcher.js
var AmiSockChat = function(auth) {
var pub = {},
watchers = new AmiWatcherCollection;
var sock = undefined,
handlers = {},
dumpPackets = false;
var selfUserId = undefined,
selfChannelName = undefined;
var wasConnected = false,
wasKicked = false,
isClosing = false;
var lastPing = undefined,
lastPong = undefined,
pingTimer = undefined,
pingWatcher = undefined;
watchers.proxy(pub);
watchers.define([
'conn:init', 'conn:ready', 'conn:lost', 'conn:error',
'ping:send', 'ping:long', 'ping:recv',
'session:start', 'session:fail', 'session:term',
'user:add', 'user:remove', 'user:update', 'user:clear',
'chan:add', 'chan:remove', 'chan:update', 'chan:clear', 'chan:focus', 'chan:join', 'chan:leave',
'msg:add', 'msg:remove', 'msg:clear',
]);
pub.setPacketDump = function(value) { dumpPackets = !!value; };
var parseUserColour = function(str) {
return str;
};
var parseUserPermsSep = undefined;
var parseUserPerms = function(str) {
if(parseUserPermsSep === undefined)
parseUserPermsSep = str.indexOf("\f") > -1 ? "\f" : ' ';
return str;
};
var parseMsgFlags = function(str) {
return {
isNameBold: str[0] !== '0',
isNameItalic: str[1] !== '0',
isNameUnderline: str[2] !== '0',
isNameSeparate: str[3] !== '0',
isPrivate: str[4] !== '0',
};
};
var stopPingWatcher = function() {
if(pingWatcher === undefined)
return;
clearTimeout(pingWatcher);
pingWatcher = undefined;
};
var startPingWatcher = function() {
if(pingWatcher !== undefined)
return;
pingWatcher = setTimeout(function() {
stopPingWatcher();
if(lastPong !== undefined)
return;
watchers.call('ping:long', pub);
}, 2000);
};
var send = function(opcode, data) {
if(!sock) return;
var msg = opcode;
if(data) msg += "\t" + data.join("\t");
if(dumpPackets) console.log(msg);
sock.send(msg);
};
var sendPing = function() {
if(!UserContext.self)
return;
watchers.call('ping:send', pub);
startPingWatcher();
lastPong = undefined;
lastPing = Date.now();
send('0', [UserContext.self.id]);
};
var sendAuth = function(args) {
send('1', args);
};
var sendMessage = function(text) {
if(!UserContext.self)
return;
send('2', [UserContext.self.id, text]);
};
var startKeepAlive = function() {
if(pingTimer !== undefined)
return;
pingTimer = setInterval(sendPing, futami.get('ping') * 1000);
};
var stopKeepAlive = function() {
if(pingTimer === undefined)
return;
clearInterval(pingTimer);
pingTimer = undefined;
};
var connect = function() {
watchers.call('conn:init', pub, [{
wasConnected: wasConnected,
}]);
ami.servers.getServer(function(server) {
sock = new WebSocket(server);
sock.addEventListener('open', onOpen);
sock.addEventListener('close', onClose);
sock.addEventListener('error', onError);
sock.addEventListener('message', onMessage);
}.bind(this));
};
var formatBotMsg = function(type, args, isError) {
var str = (isError ? '1' : '0') + "\f" + type;
if(args && args.length)
str += "\f" + args.join("\f");
return str;
};
// pong handler
handlers['0'] = function(args) {
lastPong = Date.now();
watchers.call('ping:recv', pub, [{
ping: lastPing,
pong: lastPong,
diff: lastPong - lastPing,
}]);
};
// join/auth
handlers['1'] = function(args) {
if(args[0] !== 'y' && args[0] !== 'n') {
watchers.call('user:add', pub, [{
msg: {
id: args[5],
time: new Date(parseInt(args[0]) * 1000),
text: formatBotMsg('join', [args[2]]),
},
user: {
id: args[1],
name: args[2],
colour: parseUserColour(args[3]),
perms: parseUserPerms(args[4]),
permsRaw: args[4],
},
}]);
} else {
if(args[0] === 'y') {
var userId = args[1],
channelName = args[5],
maxMsgLength = parseInt(args[6]);
selfUserId = userId;
selfChannelName = channelName;
watchers.call('session:start', pub, [{
wasConnected: wasConnected,
session: { success: true },
ctx: {
maxMsgLength: maxMsgLength,
},
user: {
id: userId,
name: args[2],
colour: parseUserColour(args[3]),
perms: parseUserPerms(args[4]),
permsRaw: args[4],
},
channel: {
name: channelName,
},
}]);
wasConnected = true;
} else {
wasKicked = true;
var info = {
session: {
success: false,
reason: args[1],
},
};
if(args[2] !== undefined)
info.baka = {
type: 'join',
perma: args[2] === '-1',
until: new Date(parseInt(args[2]) * 1000),
};
watchers.call('session:fail', pub, [info]);
}
}
};
// message add
handlers['2'] = function(args) {
var info = {
msg: {
id: args[3],
time: new Date(parseInt(args[0]) * 1000),
sender: { id: args[1] },
flags: parseMsgFlags(args[4]),
flagsRaw: args[4],
isBot: args[1] === '-1',
text: args[2],
},
};
if(info.msg.isBot) {
var botParts = args[2].split("\f");
info.msg.botInfo = {
isError: botParts[0] === '1',
type: botParts[1],
args: botParts.slice(2),
};
}
watchers.call('msg:add', pub, [info]);
};
// user leave
handlers['3'] = function(args) {
watchers.call('user:remove', pub, [{
leave: { type: args[2] },
msg: {
id: args[4],
time: new Date(parseInt(args[3]) * 1000),
text: formatBotMsg(args[2], [args[1]]),
},
user: {
id: args[0],
name: args[1],
},
}]);
};
// channel add/upd/del
handlers['4'] = {};
// channel add
handlers['4']['0'] = function(args) {
watchers.call('chan:add', pub, [{
channel: {
name: args[0],
hasPassword: args[1] !== '0',
isTemporary: args[2] !== '0',
},
}]);
};
// channel update
handlers['4']['1'] = function(args) {
watchers.call('chan:update', pub, [{
channel: {
previousName: args[0],
name: args[1],
hasPassword: args[2] !== '0',
isTemporary: args[3] !== '0',
},
}]);
};
// channel nuke
handlers['4']['2'] = function(args) {
watchers.call('chan:remove', pub, [{
channel: { name: args[0] },
}]);
};
// user channel move
handlers['5'] = {};
// user join channel
handlers['5']['0'] = function(args) {
watchers.call('chan:join', pub, [{
user: {
id: args[0],
name: args[1],
colour: parseUserColour(args[2]),
perms: parseUserPerms(args[3]),
permsRaw: args[3],
},
msg: {
id: args[4],
text: formatBotMsg('jchan', [args[1]]),
},
}]);
};
// user leave channel
handlers['5']['1'] = function(args) {
watchers.call('chan:leave', pub, [{
user: { id: args[0] },
msg: {
id: args[1],
text: formatBotMsg('lchan', [UserContext.users[args[0]].username]),
},
}]);
};
// user forced channel switch
handlers['5']['2'] = function(args) {
selfChannelName = args[0];
watchers.call('chan:focus', pub, [{
channel: { name: selfChannelName },
}]);
};
// message delete
handlers['6'] = function(args) {
watchers.call('msg:remove', pub, [{
msg: { id: args[0] },
}]);
};
// context populate
handlers['7'] = {};
// existing users
handlers['7']['0'] = function(args) {
var count = parseInt(args[0]);
for(var i = 0; i < count; i++) {
var offset = 1 + 5 * i,
userId = args[offset];
if(selfUserId === userId)
continue;
watchers.call('user:add', pub, [{
user: {
id: userId,
name: args[offset + 1],
colour: parseUserColour(args[offset + 2]),
perms: parseUserPerms(args[offset + 3]),
permsRaw: args[offset + 3],
hidden: args[offset + 4] !== '0',
},
}]);
}
};
// existing messages
handlers['7']['1'] = function(args) {
var info = {
msg: {
id: args[6],
time: new Date(parseInt(args[0]) * 1000),
sender: {
id: args[1],
name: args[2],
colour: parseUserColour(args[3]),
perms: parseUserPerms(args[4]),
permsRaw: args[4],
},
isBot: args[1] === '-1',
silent: args[7] === '0',
flags: parseMsgFlags(args[8]),
flagsRaw: args[8],
text: args[5],
},
};
if(info.msg.isBot) {
var botParts = args[5].split("\f");
info.msg.botInfo = {
isError: botParts[0] === '1',
type: botParts[1],
args: botParts.slice(2),
};
}
watchers.call('msg:add', pub, [info]);
};
// existing channels
handlers['7']['2'] = function(args) {
var count = parseInt(args[0]);
for(var i = 0; i < count; i++) {
var offset = 1 + 3 * i,
chanName = args[offset];
watchers.call('chan:add', pub, [{
channel: {
name: chanName,
hasPassword: args[offset + 1] !== '0',
isTemporary: args[offset + 2] !== '0',
isCurrent: chanName === selfChannelName,
},
}]);
}
watchers.call('chan:focus', pub, [{
channel: { name: selfChannelName },
}]);
};
// context clear
handlers['8'] = function(args) {
if(args[0] === '0' || args[0] === '3' || args[0] === '4')
watchers.call('msg:clear', pub);
if(args[0] === '1' || args[0] === '3' || args[0] === '4')
watchers.call('user:clear', pub);
if(args[0] === '2' || args[0] === '4')
watchers.call('chan:clear', pub);
};
// baka (ban/kick)
handlers['9'] = function(args) {
var bakaType = args[0],
bakaTime = parseInt(args[1]);
wasKicked = true;
var info = {
session: { success: false },
baka: {
type: bakaType === '0' ? 'kick' : 'ban',
},
};
if(info.baka.type === 'ban')
info.baka.until = new Date(bakaTime * 1000);
watchers.call('session:term', pub, [info]);
};
// user update
handlers['10'] = function(args) {
watchers.call('user:update', pub, [{
user: {
id: args[0],
name: args[1],
colour: parseUserColour(args[2]),
perms: parseUserPerms(args[3]),
permsRaw: args[3],
},
}]);
};
var onOpen = function() {
watchers.call('conn:ready', pub, [{
wasConnected: wasConnected,
}]);
startKeepAlive();
watchers.call('user:clear', pub);
watchers.call('msg:clear', pub);
watchers.call('chan:clear', pub);
var authInfo = auth.getInfo();
sendAuth([authInfo.method, authInfo.token]);
};
var onClose = function(ev) {
stopPingWatcher();
stopKeepAlive();
if(isClosing || wasKicked)
return;
watchers.call('conn:lost', pub, [{
wasConnected: wasConnected,
code: ev.code,
msg: formatBotMsg('reconnect', [], true),
}]);
};
var onError = function(ex) {
watchers.call('conn:error', pub, [ex]);
};
var onMessage = function(ev) {
var args = ev.data.split("\t"),
handler = handlers;
if(dumpPackets)
console.log(args);
for(;;) {
handler = handler[args.shift()];
if(!handler)
break;
if(typeof handler === 'function') {
handler.call(pub, args);
break;
}
}
};
pub.sendMessage = sendMessage;
pub.open = function() {
connect();
};
pub.close = function() {
isClosing = true;
};
return pub;
};

286
src/ami.js/sound.js Normal file
View file

@ -0,0 +1,286 @@
#include utility.js
var AmiSound = function() {
var playing = [];
var isMuted = false,
volume = .8;
var packName = undefined,
packs = new Map,
library = new Map;
var supported = [], fallback = [];
var formats = {
opus: 'audio/ogg;codecs=opus',
ogg: 'audio/ogg;codecs=vorbis',
mp3: 'audio/mpeg;codecs=mp3',
caf: 'audio/x-caf;codecs=opus',
wav: 'audio/wav',
};
var extractKeys = function(map) {
if('Array' in window && 'from' in Array)
return Array.from(map.keys());
var names = [];
map.forEach(function(value, key) {
names.push(key);
});
return names;
};
var createPack = function(name, title, events) {
name = (name || '').toString();
title = (title || '').toString();
events = events || new Map;
return {
getName: function() {
return name;
},
getTitle: function() {
return title;
},
getEventNames: function() {
return extractKeys(events);
},
hasEventSound: function(eventName) {
return events.has(eventName);
},
getEventSound: function(eventName) {
if(!events.has(eventName))
throw 'event not registered';
return events.get(eventName);
},
};
};
var createSound = function(name, title, sources) {
name = (name || '').toString();
title = (title || ('Nameless Sound (' + name + ')')).toString();
sources = sources || {};
return {
getName: function() {
return name;
},
getTitle: function() {
return title;
},
getSources: function() {
return sources;
},
};
};
(function() {
var elem = $e('audio');
for(var name in formats) {
var format = formats[name],
support = elem.canPlayType(format);
if(support === 'probably')
supported.push(name);
else if(support === 'maybe')
fallback.push(name);
}
})();
var findSupportedUrl = function(urls) {
if(typeof urls === 'string')
return urls;
var url = null;
for(var i in supported) {
var type = supported[i];
if(type in urls) {
url = urls[type];
break;
}
}
if(url === null)
for(var i in fallback) {
var type = fallback[i];
if(type in urls) {
url = urls[type];
break;
}
}
return url;
};
var playSound = function(urls, localVolume, rate) {
if(isMuted)
return;
var url = findSupportedUrl(urls);
if(url === undefined || url === null)
return;
// there's a caveat here where if the user changes the volume while a sound is playing
// this adjustment is essentially undone and reset back to 1.0
// it's generally only used to increase volume, but it could cause a Jump Scare for the few instances
// where it is used to lower the volume
if(localVolume === undefined)
localVolume = 1.0;
if(rate === undefined) rate = 1.0;
else rate = Math.min(4.0, Math.max(0.25, rate));
var audio = $e({
tag: 'audio',
attrs: { src: url },
});
audio.volume = Math.max(0.0, Math.min(1.0, volume * localVolume));
audio.muted = isMuted;
audio.preservesPitch = false;
audio.playbackRate = rate;
playing.push(audio);
audio.addEventListener('ended', function() {
$ari(playing, audio);
});
audio.addEventListener('loadeddata', function() {
audio.play();
});
};
var playLibrarySound = function(soundName, localVolume, rate) {
var soundInfo = library.get(soundName);
if(soundInfo === undefined)
return;
playSound(soundInfo.getSources(), localVolume, rate);
};
var pub = {};
pub.findSupportedUrl = findSupportedUrl;
pub.playSound = playSound;
pub.playLibrarySound = playLibrarySound;
pub.playEventSound = function(eventName, localVolume, rate) {
if(packName === undefined || packName === null || isMuted)
return;
var pack = packs.get(packName);
if(pack === undefined)
return;
if(typeof eventName === 'string') {
if(!pack.hasEventSound(eventName))
return;
} else {
var names = eventName;
eventName = null;
for(var i in names) {
var name = names[i];
if(pack.hasEventSound(name)) {
eventName = name;
break;
}
}
if(eventName === null)
return;
}
playLibrarySound(pack.getEventSound(eventName), localVolume, rate);
};
pub.getPackName = function() { return packName; };
pub.setPackName = function(name) { packName = name; };
pub.forEachPack = function(body) {
packs.forEach(function(value) {
body(value);
});
};
pub.hasPacks = function() {
return packs.size > 0;
};
pub.getPacks = function() {
var arr = [];
packs.forEach(function(value) {
arr.push(value);
});
return arr;
};
pub.clearPacks = function() {
packs.clear();
};
var loadSoundFormats = ['opus', 'caf', 'ogg', 'mp3', 'wav'];
pub.registerLibrarySound = function(soundInfo) {
if(typeof soundInfo !== 'object')
throw 'soundInfo must be an object';
if(typeof soundInfo.name !== 'string')
throw 'soundInfo must contain a name field';
if(typeof soundInfo.sources !== 'object')
throw 'soundInfo must contain a sources field';
var sources = {},
sCount = 0;
for(var i in loadSoundFormats) {
var fmt = loadSoundFormats[i];
if(typeof soundInfo.sources[fmt] !== 'string')
continue;
sources[fmt] = soundInfo.sources[fmt];
++sCount;
}
if(sCount < 1)
throw 'soundInfo does not contain any valid sources';
library.set(soundInfo.name, createSound(soundInfo.name, soundInfo.title, sources));
};
pub.registerSoundPack = function(packInfo) {
if(typeof packInfo !== 'object')
throw 'packInfo must be an object';
if(typeof packInfo.name !== 'string')
throw 'packInfo must contain a name field';
if(typeof packInfo.events !== 'object')
throw 'packInfo must contain a events field';
var events = new Map;
for(var eventName in packInfo.events) {
if(typeof packInfo.events[eventName] !== 'string')
continue;
events.set(eventName, packInfo.events[eventName]);
}
if(events.size < 1)
throw 'packInfo does not contain any valid events';
packs.set(packInfo.name, createPack(packInfo.name, packInfo.title, events));
};
pub.hasSoundLibrary = function() {
return library.size > 0;
};
pub.clearSoundLibrary = function() {
library.clear();
};
pub.getVolume = function() { return volume; };
pub.setVolume = function(value) {
volume = Math.max(0.0, Math.min(1.0, value));
for(var i in playing)
playing[i].volume = volume;
};
pub.isMuted = function() {
return isMuted;
};
pub.setMuted = function(mute) {
isMuted = !!mute;
for(var i in playing)
playing[i].muted = isMuted;
};
return pub;
};

23
src/ami.js/status.js Normal file
View file

@ -0,0 +1,23 @@
#include utility.js
var AmiStatusIndicator = function(parent) {
var icon = $e({ attrs: { id: 'statusIconContainer' } });
parent.appendChild(icon);
var setState = function(colour, text) {
icon.className = 'status-' + colour;
icon.title = (text || '').toString();
};
return {
setRed: function(text) {
setState('red', text);
},
setYellow: function(text) {
setState('yellow', text);
},
setGreen: function(text) {
setState('green', text);
},
};
};

283
src/ami.js/strings.js Normal file
View file

@ -0,0 +1,283 @@
var AmiStrings = (function() {
var menuText = {
chans: "Channels",
online: "Online Users",
sets: "Settings",
uploads: "Uploads",
help: "Help",
submit: "Submit",
send: "Send",
kick: "You have been kicked!",
ban: "You have been banned until",
authfail: "Could not join chat: Authorization data supplied was denied! Contact the system administrator if the problem persists.",
userfail: "Could not join chat: Username in use!",
sockfail: "Could not join chat: A session already exists from this socket!",
joinfail: "Could not join chat: You are banned until",
conn: "Connecting to chat server...",
auth: "Authenticating login data...",
term: "Connection terminated! Details: ",
back: "Click here to go back",
eot: "the end of time.",
enable: "Enable {0} Tag",
persist: "Persist {0} Tag",
bprompt: "Enter the argument:",
urlprompt: "Enter the URL:",
chanpwd: "Enter the password for {0}:",
username: "Username",
message: "Message",
whisper: "(whispers)",
whisperto: "(whispers to {0})",
delmsg: "Are you sure you want to delete this message?",
ctxlou: "List online users",
ctxlchan: "List available channels",
ctxda: "Describe action",
ctxnick: "Change username",
ctxcchan: "Create channel",
ctxjchan: "Join a channel",
ctxpm: "Send private message",
ctxkick: "Kick user",
ctxban: "Ban user",
ctxwho: "Display IP Address",
};
var botText = {
say: "{0}",
join: "{0} has joined the chat.",
leave: "{0} has disconnected.",
jchan: "{0} entered the channel.",
lchan: "{0} left the channel.",
kick: "{0} has been kicked.",
timeout: "{0} exploded.",
nick: "{0} is now known as {1}.",
crchan: "{0} has been created.",
delchan: "{0} has been deleted.",
cpwdchan: "Password changed successfully.",
cprivchan: "Channel privilege changed successfully.",
ipaddr: "User {0} - IP Address: {1}",
silence: "You have been silenced!",
unsil: "You have been unsilenced!",
usilok: "{0} has been unsilenced.",
silok: "{0} has been silenced.",
flood: "{0} has been kicked for flood protection.",
flwarn: "[b]Warning! You will be kicked for flood protection if you continue.[/b]",
unban: "{0} has been unbanned.",
banlist: "Banned Users/IPs: {0}",
who: "Online Users: {0}",
whochan: "Users in {0}: {1}",
};
var botErrText = {
nolang: "Error: Chatbot sent {1} id '{0}' with no definition in this language!",
nocmd: "Error: Command {0} not found!",
cmdna: "Error: Not allowed to use {0}!",
cmderr: "Error: Command formatted incorrectly!",
usernf: "Error: User {0} not found!",
kickna: "Error: Not allowed to kick {0}!",
samechan: "Error: You are already in {0}!",
nochan: "Error: Channel {0} does not exist!",
ipchan: "Error: You are not allowed to join {0}!",
nopwchan: "Error: Channel {0} is password-protected! Use '/join {0} [password]'.",
ipwchan: "Error: Password to channel {0} is incorrect!",
inchan: "Error: Channel name cannot start with a @ or *!",
nischan: "Error: Channel {0} already exists!",
ndchan: "Error: You are not allowed to delete channel {0}!",
namchan: "Error: You are not allowed to modify channel {0}!",
nameinuse: "Error: Username {0} in use!",
rankerr: "Error: Cannot set channel privilege higher than your own!",
reconnect: "Connection lost! Attempting to reconnect automatically...",
logerr: "Server uses the flat file database implementation and cannot serve logs to clients. To view chat logs, open the logs.txt file in the ffdb folder in the server's root directory.",
generr: "An error has occurred!",
logimper: "You are not allowed to view the logs.",
silerr: "Error: User is already silenced!",
usilerr: "Error: User is not silenced!",
silperr: "Error: You are not allowed to silence this user!",
usilperr: "Error: You are not allowed to unsilence this user!",
silself: "Error: You cannot silence yourself!",
delerr: "Error: Not allowed to delete this message!",
notban: "Error: {0} is not banned!",
whoerr: "Error: Not allowed to look into channel {0}!",
};
var settingsText = {
bbcode: "Enable BBCode",
emotes: "Enable emoticons",
links: "Enable hyperlinks",
volume: "Sound volume",
spack: "Sound pack",
style: "Style",
runjoketrig: "Run joke triggers",
shownotifs: "Enable notifications",
dumppackets: "Dump packets to console",
autoembedmedia: "Auto-embed media",
skipdomainpopupthing: "Skip domain pop up thing",
};
var bbCodeText = {
b: "Bold",
i: "Italics",
u: "Underline",
s: "Strikethrough",
quote: "Quote",
code: "Code",
sjis: "SJIS",
url: "Link",
img: "Image",
video: "Video",
audio: "Audio",
color: "Colour",
spoiler: "Spoiler",
};
var helpText = [
{
title: "Send Private Message",
format: "/msg name message",
},
{
title: "Describe an Action",
format: "/me description",
},
{
title: "Away From Keyboard",
format: "/afk [reason]",
},
{
title: "List Users",
format: "/who [channel]",
},
{
title: "Create a Channel",
format: "/create [rank] name",
},
{
title: "Join a Channel",
format: "/join name",
},
{
title: "Set Channel Password",
format: "/pwd [password]",
},
{
title: "Set Channel Rank",
format: "/rank [rank]",
},
{
title: "Kick User (moderator only)",
format: "/kick user [time]",
modOnly: true,
},
{
title: "Ban User (moderator only)",
format: "/ban user [time]",
modOnly: true,
},
{
title: "Unban User (moderator only)",
format: "/unban user",
modOnly: true,
},
{
title: "Unban IP (moderator only)",
format: "/unbanip ip",
modOnly: true,
},
{
title: "Silence User (moderator only)",
format: "/silence user [time]",
modOnly: true,
},
{
title: "Unsilence User (moderator only)",
format: "/unsilence user",
modOnly: true,
},
{
title: "Server-Wide Broadcast (moderator only)",
format: "/say message",
modOnly: true,
},
{
title: "Get User IP (moderator only)",
format: "/whois user",
modOnly: true,
},
{
title: "Delete Channel (moderator only)",
format: "/delete name",
modOnly: true,
},
];
var formatStr = function(string, args) {
if(args)
for(var i = 0; i < args.length; ++i) {
var arg = args[i];
string = string.replace(new RegExp('\\{' + i + '\\}', 'g'), arg);
}
return string;
};
return {
hasMenuString: function(name) {
return name in menuText;
},
getMenuString: function(name, args) {
if(name in menuText)
return formatStr(menuText[name], args);
return 'getMenuString(' + name + ')';
},
hasBBCodeString: function(name) {
return name in bbCodeText;
},
getBBCodeString: function(name) {
if(name in bbCodeText)
return bbCodeText[name];
return name;
},
hasSettingsString: function(name) {
return name in settingsText;
},
getSettingsString: function(name) {
if(name in settingsText)
return settingsText[name];
return 'getSettingsString(' + name + ')';
},
parseBotMessage: function(text) {
var parts = text.split("\f"),
isError = parts[0] !== '0',
type = parts[1],
args = parts.slice(2),
string = null;
if(isError) {
if(type in botErrText)
string = botErrText[type];
} else {
if(type in botText)
string = botText[type];
}
if(string === null) {
args = [type, isError ? 'error' : 'message'];
isError = true;
type = 'nolang';
string = botErrText[type];
}
string = formatStr(string, args);
return {
isError: isError,
type: type,
args: args,
string: string,
html: ('<span class="' + (isError ? 'botError' : 'botMessage') + '">' + string + '</span>'),
};
},
forEachHelpString: function(body) {
for(var i = 0; i < helpText.length; ++i)
body(helpText[i]);
},
};
})();

47
src/ami.js/styles.js Normal file
View file

@ -0,0 +1,47 @@
var AmiStyles = function() {
var styles = new Map,
defaultStyle = undefined;
var createStyle = function(name, title, setDefault) {
if(setDefault)
defaultStyle = name;
return {
getName: function() { return name; },
getTitle: function() { return title; },
isDefault: function() { return name === defaultStyle; },
getBodyClass: function() { return 'ami-theme-' + name; },
getOptionClass: function() { return 'ami-themeOpt-' + name; },
};
};
var apply = function(name) {
var style = styles.get(name);
if(style === undefined)
throw 'this style has not been registered';
document.body.className = style.getBodyClass();
};
return {
register: function(name, title, setDefault) {
if(styles.has(name))
throw 'a style with this name has already been registered';
var styleInfo = createStyle(name, title, setDefault);
styles.set(styleInfo.getName(), styleInfo);
return styleInfo;
},
get: function(name) {
var style = styles.get(name);
if(style === undefined)
throw 'this style has not been registered';
return styles.get(name);
},
forEach: function(body) {
styles.forEach(body);
},
apply: apply,
setDefault: function() {
apply(defaultStyle);
},
};
};

66
src/ami.js/submitbox.js Normal file
View file

@ -0,0 +1,66 @@
#include utility.js
#include watcher.js
var AmiSubmitBox = function(parent) {
var pub = {},
watcher = new AmiWatcher,
maxLengthValue = 0,
click = function(ev) {
watcher.call(pub, [ev]);
};
var button = $e({
tag: 'input',
attrs: {
type: 'button',
value: 'Send',
id: 'sendmsg',
onclick: function(ev) { click(ev); },
},
}),
curLength = $e({
tag: 'span',
child: '0',
}),
maxLength = $e({
tag: 'span',
child: maxLengthValue.toString(),
}),
container = $e({
attrs: { id: 'submitButtonContainer' },
child: [
{
tag: 'span',
attrs: { id: 'messageLengthCounter' },
child: [curLength, '/', maxLength],
},
' ',
button,
],
});
parent.appendChild(container);
pub.click = click;
pub.watch = function(callback) {
watcher.watch(callback);
};
pub.unwatch = function(watcher) {
watcher.unwatch(callback);
};
pub.setCurrentLength = function(num) {
num = parseInt(num);
curLength.textContent = num.toLocaleString();
if(num >= maxLengthValue && !curLength.style.color)
curLength.style.color = '#c00';
else if(num < maxLengthValue && curLength.style.color)
curLength.style.color = null;
};
pub.setMaxLength = function(num) {
maxLengthValue = parseInt(num);
maxLength.textContent = maxLengthValue.toLocaleString();
};
return pub;
};

67
src/ami.js/title.js Normal file
View file

@ -0,0 +1,67 @@
#include common.js
#include utility.js
var AmiChatTitle = function(parent, title) {
var elem = $e({ attrs: { id: 'chatTitle' }, child: title});
parent.appendChild(elem);
return {
setTitle: function(title) {
elem.textContent = title;
},
};
};
var AmiWindowTitle = function(title) {
var baseTitle = '',
activeFlash = undefined;
var setTitle = function(text) {
document.title = text;
};
var setBaseTitle = function(title) {
baseTitle = title;
if(activeFlash === undefined)
setTitle(baseTitle);
};
setBaseTitle(title);
var clearTitle = function() {
if(activeFlash !== undefined) {
clearInterval(activeFlash);
activeFlash = undefined;
setTitle(baseTitle);
}
};
var flashTitle = function(titles, interval, repeat) {
if(interval === undefined) interval = 500;
if(repeat === undefined) repeat = 5;
var round = 0,
target = titles.length * repeat;
clearTitle();
setTitle(titles[0]);
activeFlash = setInterval(function() {
if(round >= target) {
clearTitle();
setTitle(baseTitle);
return;
}
++round;
setTitle(titles[round % titles.length]);
}, interval);
};
return {
setBaseTitle: setBaseTitle,
setTitle: setTitle,
clearTitle: clearTitle,
flashTitle: flashTitle,
};
};

40
src/ami.js/ts_10_user.js Normal file
View file

@ -0,0 +1,40 @@
#include common.js
var User = (function () {
function User(id, u, c, p, v) {
if (v === void 0) { v = true; }
this.username = u;
this.id = id;
this.color = c;
this.permstr = p;
this.perms = p.split(p.indexOf("\f") < 0 ? ' ' : "\f");
this.visible = v;
}
User.prototype.EvaluatePermString = function () {
this.perms = this.permstr.split(this.permstr.indexOf("\f") < 0 ? ' ' : "\f");
};
User.prototype.getRank = function () {
return parseInt(this.perms[0]);
};
User.prototype.canModerate = function () {
return this.perms[1] == "1";
};
User.prototype.isBot = function() {
return this.id === '-1';
};
User.prototype.getAvatarUrl = function() {
if(this.isBot())
return '/images/alert.png';
return futami.get('avatar')
.replace('{resolution}', '160')
.replace('{user:id}', this.id)
.replace('{user:avatar_change}', '9001');
};
return User;
})();
var UserContext = (function () {
function UserContext() {
}
UserContext.users = {};
return UserContext;
})();

361
src/ami.js/ts_20_ui.js Normal file
View file

@ -0,0 +1,361 @@
#include ts_20_ui.js
#include ts_chat.js
#include cookies.js
#include ts_10_user.js
#include ts_utils.js
#include title.js
#include strings.js
#include utility.js
var UI = (function () {
function UI() {}
UI.RenderLanguage = function () {
document.getElementById("sendmsg").value = AmiStrings.getMenuString('send');
try {
document.getElementById("namelabel").innerHTML = AmiStrings.getMenuString('username');
} catch (e) {}
try {
document.getElementById("msglabel").innerHTML = AmiStrings.getMenuString('message');
} catch (e) {}
var rows = document.getElementById("settingsList").getElementsByTagName("table")[0].getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
if (rows[i].getAttribute("name").substr(0, 2) == ";;") {
var code = rows[i].getAttribute("name").substr(2);
rows[i].cells[0].innerHTML = AmiStrings.getMenuString('persist', [AmiStrings.getBBCodeString(code)]);
}
else if (rows[i].getAttribute("name").substr(0, 2) == "||") {
var code = rows[i].getAttribute("name").substr(2);
rows[i].cells[0].innerHTML = AmiStrings.getMenuString('enable', [AmiStrings.getBBCodeString(code)]);
}
else {
if(AmiStrings.hasSettingsString(rows[i].getAttribute('name')))
rows[i].cells[0].innerHTML = AmiStrings.getSettingsString(rows[i].getAttribute('name')) + ":";
}
}
var btns = document.getElementById("bbCodeContainer").getElementsByTagName("input");
for (var i = 0; i < btns.length; i++) {
if (btns[i].getAttribute("name") != ";;")
btns[i].value = AmiStrings.hasBBCodeString(btns[i].name) ? AmiStrings.getBBCodeString(btns[i].name) : btns[i].name;
}
if (UserContext.self != undefined)
UI.RedrawUserList();
};
UI.GenerateContextMenu = function (u) {
var ret = document.createElement("ul");
ret.id = "sock_menu_" + u.id;
ret.className = "userMenu";
if (UserContext.self.id !== u.id)
ret.classList.add('hidden');
UI.contextMenuFields.forEach(function (elt, i, arr) {
if ((UserContext.self.id === u.id && elt["self"]) || (elt["others"] && UserContext.self.id !== u.id)) {
if ((UserContext.self.canModerate() && elt["modonly"]) || !elt["modonly"]) {
if (AmiStrings.hasMenuString(elt.langid)) {
var li = document.createElement("li");
var link = document.createElement("a");
link.href = "javascript:void(0);";
link.innerHTML = AmiStrings.getMenuString(elt.langid);
switch (elt["action"]) {
default:
case 0:
var msg = Utils.replaceAll(Utils.replaceAll(elt["value"], "{0}", "{ {0} }"), "{1}", "{ {1} }");
link.onclick = function (e) {
ami.chat.inputBox.setValue(Utils.replaceAll(Utils.replaceAll(msg, "{ {0} }", UserContext.self.username), "{ {1} }", u.username) + " ");
ami.chat.inputBox.focus();
};
break;
case 1:
var msg = Utils.replaceAll(Utils.replaceAll(elt["value"], "{0}", "{ {0} }"), "{1}", "{ {1} }");
link.onclick = function (e) {
ami.sockChat.sendMessage(Utils.replaceAll(Utils.replaceAll(msg, "{ {0} }", UserContext.self.username), "{ {1} }", u.username));
};
break;
}
li.appendChild(link);
ret.appendChild(li);
}
}
}
});
return ret;
};
UI.AddMessage = function (msgid, date, u, msg, strobe, playsound, flags) {
if(strobe === undefined) strobe = true;
if(playsound === undefined) playsound = true;
if(flags === undefined) flags = '10010';
if(date === null || date === undefined)
date = new Date;
else if(typeof date === 'number')
date = new Date(date * 1000);
var msgInfo = {
id: msgid,
sender: u,
created: date,
nameBold: flags[0] !== '0',
nameItalics: flags[1] !== '0',
nameUnderline: flags[2] !== '0',
showColon: flags[3] !== '0',
isPM: flags[4] !== '0',
canDelete: 'self' in UserContext
&& !isNaN(parseInt(msgid))
&& !u.isBot()
&& UserContext.self.canModerate()
&& (UserContext.self.id === u.id || UserContext.self.getRank() > u.getRank()),
};
var botInfo = null,
outmsg = msg;
if(u.isBot()) {
botInfo = AmiStrings.parseBotMessage(msg);
outmsg = botInfo.html;
}
if(playsound && ami.settings.get('runJokeTriggers'))
try {
var trigger = ami.textTriggers.getTrigger(msg);
if(trigger.isSoundType()) {
ami.sound.playLibrarySound(
trigger.getRandomSoundName(),
trigger.getVolume(),
trigger.getRate()
);
playsound = false;
}
} catch(ex) {}
if(playsound) {
if(botInfo)
ami.sound.playEventSound([botInfo.type, botInfo.isError ? 'error' : 'server']);
else if(UserContext.self.id === u.id)
ami.sound.playEventSound('outgoing');
else if(msgInfo.isPM)
ami.sound.playEventSound(['incoming-priv', 'incoming']);
else
ami.sound.playEventSound('incoming');
}
var mention = 'self' in UserContext && (outmsg.toLowerCase()).indexOf(UserContext.self.username.toLowerCase()) >= 0;
if(msgInfo.isPM && UserContext.self.id === u.id) {
var p = outmsg.split(' ');
msgInfo.pmTargetName = p[0];
outmsg = p.slice(1).join(' ');
}
if(strobe && ami.pageIsHidden() && (msgInfo.isPM || mention)) {
var strip = outmsg.replace(new RegExp("\\[.*?\\]", "g"), "").replace(new RegExp("\\<.*?\\>", "g"), "");
ami.notifications.display({
title: u.username + ' mentioned you!',
text: strip,
icon: u.getAvatarUrl(),
});
}
if(ami.settings.get('bbParse'))
for(var i = 0; i < UI.bbcode.length; i++) {
if(!UI.bbcode[i]["arg"]) {
var at = 0;
while((at = outmsg.indexOf("[" + UI.bbcode[i]['tag'] + "]", at)) != -1) {
var end;
if ((end = outmsg.indexOf("[/" + UI.bbcode[i]['tag'] + "]", at)) != -1) {
var inner = Utils.StripCharacters(outmsg.substring(at + ("[" + UI.bbcode[i]['tag'] + "]").length, end), UI.bbcode[i]["rmin"] == undefined ? "" : UI.bbcode[i]["rmin"]);
var replace = Utils.replaceAll(UI.bbcode[i]['swap'], "{0}", inner);
if (UI.bbcode[i].enableSetting && !ami.settings.get(UI.bbcode[i].enableSetting))
replace = inner;
outmsg = outmsg.substring(0, at) + replace + outmsg.substring(end + ("[/" + UI.bbcode[i]['tag'] + "]").length);
at += replace.length;
} else break;
}
} else {
var at = 0;
while((at = outmsg.indexOf("[" + UI.bbcode[i]['tag'] + "=", at)) != -1) {
var start, end;
if ((start = outmsg.indexOf("]", at)) != -1) {
if ((end = outmsg.indexOf("[/" + UI.bbcode[i]['tag'] + "]", start)) != -1) {
var arg = Utils.StripCharacters(outmsg.substring(at + ("[" + UI.bbcode[i]['tag'] + "=").length, start), "[]" + (UI.bbcode[i]["rmarg"] == undefined ? "" : UI.bbcode[i]["rmarg"]));
var inner = Utils.StripCharacters(outmsg.substring(start + 1, end), UI.bbcode[i]["rmin"] == undefined ? "" : UI.bbcode[i]["rmin"]);
var replace = Utils.replaceAll(Utils.replaceAll(UI.bbcode[i]['swap'], "{1}", inner), "{0}", arg);
if (UI.bbcode[i].enableSetting && !ami.settings.get(UI.bbcode[i].enableSetting))
replace = inner;
outmsg = outmsg.substring(0, at) + replace + outmsg.substring(end + ("[/" + UI.bbcode[i]['tag'] + "]").length);
at += replace.length;
} else break;
} else break;
}
}
}
if(ami.settings.get('parseLinks')) {
var tmp = outmsg.split(' ');
for(var i = 0; i < tmp.length; i++) {
if(tmp[i].indexOf("<") != -1 && tmp[i].indexOf(">") == -1) {
tmp[i + 1] = tmp[i] + " " + tmp[i + 1];
tmp[i] = "";
continue;
}
var text = tmp[i].replace(/(<([^>]+)>)/ig, "");
if(text.substr(0, 7) == "http://" || text.substr(0, 8) == "https://")
tmp[i] = "<a href='" + text + "' target='_blank'>" + text + "</a>";
}
outmsg = tmp.join(" ");
}
if(ami.settings.get('parseEmoticons'))
ami.emoticons.forEach(u.getRank(), function(emote) {
var args = [];
for(var i in emote.strings)
args.push(Utils.SanitizeRegex(Utils.Sanitize(':' + emote.strings[i] + ':')));
outmsg = outmsg.replace(new RegExp("(" + args.join("|") + ")(?![^\\<]*\\>)", "g"), '<img src="' + emote.url + '" class="chatEmote" />');
});
msgInfo.bodyRaw = outmsg;
var msgElem = ami.chat.messageList.add(msgInfo);
if(ami.settings.get('bbAutoEmbedV1')) {
var buttons = msgElem.getElementsByClassName('js-embed-btn');
while(buttons.length > 0)
buttons[0].click();
}
if(ami.settings.get('msgAutoScroll'))
ami.chat.messageList.scrollToBottom();
if(strobe && ami.pageIsHidden()) {
var title = ' ' + (botInfo ? botInfo.string : u.username);
ami.windowTitle.flashTitle(['[ @]' + title, '[@ ]' + title]);
}
};
UI.ToggleUserMenu = function (id) {
var menu = document.getElementById("sock_menu_" + id);
menu.classList[menu.classList.contains('hidden') ? 'remove' : 'add']('hidden');
};
UI.AddUser = function (u, addToContext) {
if (addToContext === void 0) { addToContext = true; }
if (u.visible) {
var msgDiv = document.createElement("div");
msgDiv.className = (this.rowEven[1]) ? "rowEven" : "rowOdd";
msgDiv.id = "sock_user_" + u.id;
msgDiv.innerHTML = "<a style='color:" + u.color + "; display: block;' href='javascript:UI.ToggleUserMenu(" + u.id + ");'>" + u.username + "</a>";
msgDiv.appendChild(UI.GenerateContextMenu(u));
$i('userList').getElementsByClassName('body')[0].appendChild(msgDiv);
this.rowEven[1] = !this.rowEven[1];
}
if (addToContext) {
UserContext.users[u.id] = u;
}
};
UI.ModifyUser = function (u) {
var tmp = document.getElementById("sock_user_" + u.id).getElementsByTagName("a")[0];
tmp.style.color = u.color;
tmp.innerHTML = u.username;
};
UI.MakeChannelDisplayName = function(name, ispwd, istemp) {
var displayName = '';
if(ispwd)
displayName += '*';
if(istemp)
displayName += '[';
displayName += name;
if(istemp)
displayName += ']';
return displayName;
};
UI.SwitchChannel = function(name) {
var channels = $c('js-sidebar-channel');
for(var i = 0; i < channels.length; ++i) {
var channel = channels[i];
if(channel.style.fontWeight === 'bold')
channel.style.fontWeight = null;
}
$i('sock_chan_' + name).style.fontWeight = 'bold';
};
UI.AddChannel = function (name, ispwd, istemp, iscurr) {
var displayName = UI.MakeChannelDisplayName(name, ispwd, istemp);
var html = $e({
attrs: {
classList: ['js-sidebar-channel', this.rowEven[2] ? "rowEven" : "rowOdd"],
id: 'sock_chan_' + name,
},
child: {
tag: 'a',
child: displayName,
attrs: {
style: { display: 'block' },
href: 'javascript:void(0);',
onclick: function() {
var cmd = '/join ' + name;
if(ispwd && !UserContext.self.canModerate())
cmd += ' ' + prompt(AmiStrings.getMenuString('chanpwd', [name]));
ami.sockChat.sendMessage(cmd);
},
},
}
});
if(iscurr)
html.style.fontWeight = 'bold';
$i('chanList').getElementsByClassName('body')[0].appendChild(html);
this.rowEven[2] = !this.rowEven[2];
};
UI.ModifyChannel = function (oldname, newname, ispwd, istemp) {
var displayName = UI.MakeChannelDisplayName(newname, ispwd, istemp);
$i('sock_chat_' + oldname).children[0].textContent = displayName;
};
UI.RemoveChannel = function (name) {
$ri('sock_chat_' + oldname);
};
UI.RemoveUser = function (id) {
delete UserContext.users[id];
this.RedrawUserList();
};
UI.RedrawChannelList = function() {
$i('chanList').getElementsByClassName('top')[0].innerHTML = AmiStrings.getMenuString('chans');
$i('chanList').getElementsByClassName('body')[0].innerHTML = '';
this.rowEven[2] = false;
};
UI.RedrawUserList = function () {
$i('userList').getElementsByClassName('top')[0].innerHTML = AmiStrings.getMenuString('online');
$i('userList').getElementsByClassName('body')[0].innerHTML = '';
this.rowEven[1] = false;
if(UserContext.self !== undefined)
this.AddUser(UserContext.self, false);
for (var key in UserContext.users) {
if (UserContext.users[key].visible)
this.AddUser(UserContext.users[key], false);
}
};
UI.rowEven = [true, false, false];
UI.ChatBot = new User('-1', 'Misaka-' + (Math.ceil(20000 * Math.random()) + 1).toString(), 'inherit', '');
UI.contextMenuFields = [];
UI.bbcode = [];
return UI;
})();

634
src/ami.js/ts_chat.js Normal file
View file

@ -0,0 +1,634 @@
#include ts_20_ui.js
#include ts_10_user.js
#include eeprom.js
#include z_eepromv1.js
#include utility.js
#include sidebars/channelssb.js
#include sidebars/helpsb.js
#include sidebars/settingssb.js
#include sidebars/uploadssb.js
#include sidebars/userssb.js
var Chat = (function () {
// pulling all the env shit in atop here for now
var chatSettings = [
{
id: "style",
name: 'style',
type: "select",
load: function(input) {
ami.styles.forEach(function(style) {
input.appendChild($e({
tag: 'option',
child: style.getTitle(),
attrs: {
value: style.getName(),
className: style.getOptionClass(),
},
}));
if(style.isDefault() && !ami.settings.has('style'))
ami.settings.set('style', style.getName());
});
},
},
{
id: "bbcode",
name: 'bbParse',
type: "checkbox",
},
{
id: "emotes",
name: 'parseEmoticons',
type: "checkbox",
},
{
id: "links",
name: 'parseLinks',
type: "checkbox",
},
{
id: "spack",
name: 'soundPack',
type: "select",
load: function(input) {
ami.sound.forEachPack(function(pack) {
var opt = document.createElement('option');
opt.value = pack.getName();
opt.innerHTML = pack.getTitle();
input.appendChild(opt);
if(ami.settings.get('soundPack') === '')
ami.settings.set('soundPack', pack.getName());
});
},
},
{
id: "volume",
name: 'soundVolume',
type: "select",
as: 'float',
options: [
[1.0, '100%'],
[0.9, '90%'],
[0.8, '80%'],
[0.7, '70%'],
[0.6, '60%'],
[0.5, '50%'],
[0.4, '40%'],
[0.3, '30%'],
[0.2, '20%'],
[0.1, '10%'],
[0.0, '0%'],
],
},
{
id: "shownotifs",
name: 'enableNotifs',
type: "checkbox",
},
{
id: "runjoketrig",
name: 'runJokeTriggers',
type: "checkbox",
},
{
id: "dumppackets",
name: 'dumpPackets',
type: "checkbox",
},
{
id: "autoembedmedia",
name: 'bbAutoEmbedV1',
type: "checkbox",
},
{
id: "skipdomainpopupthing",
name: 'tmpSkipDomainPopUpThing',
type: "checkbox",
},
];
var userMenu = [
{
self: true,
others: false,
modonly: false,
langid: "ctxlou",
action: 1,
value: "/who"
},
{
self: true,
others: false,
modonly: false,
langid: "ctxlchan",
action: 1,
value: "/channels"
},
{
self: true,
others: false,
modonly: false,
langid: "ctxjchan",
action: 0,
value: "/join"
},
{
self: true,
others: false,
modonly: false,
langid: "ctxcchan",
action: 0,
value: "/create"
},
{
self: true,
others: false,
modonly: false,
langid: "ctxda",
action: 0,
value: "/me"
},
{
self: true,
others: false,
modonly: false,
langid: "ctxnick",
action: 0,
value: "/nick"
},
{
self: false,
others: true,
modonly: false,
langid: "ctxpm",
action: 0,
value: "/msg {1}"
},
{
self: false,
others: true,
modonly: true,
langid: "ctxkick",
action: 0,
value: "/kick {1}"
},
{
self: false,
others: true,
modonly: true,
langid: "ctxban",
action: 0,
value: "/ban {1}"
},
{
self: false,
others: true,
modonly: true,
langid: "ctxwho",
action: 1,
value: "/whois {1}"
},
];
var bbcodes = [
{
tag: "b",
arg: false,
swap: "<b>{0}</b>",
button: 'b',
buttonStyle: { fontWeight: 'bold' },
persistSetting: 'bbPersistBold',
persistStyleAttr: 'fontWeight',
persistStyleValue: 'bold',
},
{
tag: "i",
arg: false,
swap: "<i>{0}</i>",
button: 'i',
buttonStyle: { fontStyle: 'italic' },
persistSetting: 'bbPersistItalics',
persistStyleAttr: 'fontStyle',
persistStyleValue: 'italic',
},
{
tag: "u",
arg: false,
swap: "<u>{0}</u>",
button: 'u',
buttonStyle: { textDecoration: 'underline' },
persistSetting: 'bbPersistUnderline',
persistStyleAttr: 'textDecoration',
persistStyleValue: 'underline',
},
{
tag: "s",
arg: false,
swap: "<del>{0}</del>",
button: 's',
buttonStyle: { textDecoration: 'line-through' },
persistSetting: 'bbPersistStrike',
persistStyleAttr: 'textDecoration',
persistStyleValue: 'line-through',
},
{
tag: "quote",
arg: false,
swap: "<q style='font-variant: small-caps;'>{0}</q>",
button: true
},
{
tag: "code",
arg: false,
swap: "<span style='white-space: pre-wrap; font-family: monospace; font-size: 1.2em;'>{0}</span>",
button: true
},
{
tag: "sjis",
arg: false,
swap: "<span class='sjis'>{0}</span>",
button: true,
persistSetting: 'bbPersistSjis',
persistClassName: 'sjis',
},
{
tag: "color",
arg: true,
rmarg: ";:{}<>&|\\/~'\"",
swap: "<span style='color:{0};'>{1}</span>",
button: true,
bhandle: function(bb, ev) {
ami.chat.colourPicker.show(ev, function(colour) {
Chat.InsertBBCode('color', colour.hex);
});
},
enableSetting: 'bbParseColour',
persistSetting: 'bbPersistColour',
persistStyleAttr: 'color',
persistStyleSingle: true,
},
{
tag: "url",
arg: false,
rmin: "\"'",
swap: "<a href='{0}' target='_blank'>{0}</a>"
},
{
tag: "url",
arg: true,
rmarg: "\"'",
swap: "<a href='{0}' target='_blank'>{1}</a>",
button: true,
bprompt: "urlprompt"
},
{
tag: "img",
arg: false,
rmin: "\"'",
swap: "<span title='{0}'><span title='link'><a href='{0}' target='_blank'>{0}</a></span>&nbsp;[<a href='#' onclick='Utils.EmbedImage(this);return false;' class='js-embed-btn'>Embed</a>]</span>",
button: true,
enableSetting: 'bbParseImage',
},
{
tag: "video",
arg: false,
rmin: "\"'",
swap: "<span title='{0}'><span title='link'><a href='{0}' target='_blank'>{0}</a></span>&nbsp;[<a href='#' onclick='Utils.EmbedVideo(this);return false;' class='js-embed-btn'>Embed</a>]</span>",
button: true,
enableSetting: 'bbParseVideo',
},
{
tag: "audio",
arg: false,
rmin: "\"'",
swap: "<span title='{0}'><span title='link'><a href='{0}' target='_blank'>{0}</a></span>&nbsp;[<a href='#' onclick='Utils.EmbedAudio(this);return false;' class='js-embed-btn'>Embed</a>]</span>",
button: true,
enableSetting: 'bbParseAudio',
},
{
tag: "spoiler",
arg: false,
rmin: "\"'",
swap: "<span data-shit='{0}'><span>*** HIDDEN ***</span>&nbsp;[<a href='#' onclick='Utils.ToggleSpoiler(this);return false;'>Reveal</a>]</span>",
button: true
}
];
function Chat() {}
Chat.Main = function(auth) {
bbcodes.forEach(function(item) {
UI.bbcode.push(item);
});
userMenu.forEach(function(item) {
UI.contextMenuFields.push(item);
});
ami.chat.sidebars.create(new AmiUsersSidebar);
ami.chat.sidebars.create(new AmiChannelsSidebar);
var sbSettings = new AmiSettingsSidebar;
ami.chat.sidebars.create(sbSettings);
ami.chat.sidebars.create(new AmiHelpSidebar);
ami.chat.sidebars.create(new AmiUploadsSidebar);
if(window.innerWidth > 800)
ami.chat.sidebars.show('userList');
var table = document.getElementById("settingsList").getElementsByTagName("table")[0];
var cnt = 0;
chatSettings.forEach(function (elt, i, arr) {
var row = table.insertRow(i);
row.className = cnt % 2 == 0 ? "rowOdd" : "rowEven";
row.setAttribute("name", elt.id);
var cell = row.insertCell(0);
cell.innerHTML = elt.id;
cell = row.insertCell(1);
cell.className = "setting";
var input = null;
switch(elt.type) {
case "select":
input = document.createElement("select");
input.id = 'chatSetting-' + elt.id;
input.onchange = function(e) {
var value = this.value;
if(elt.as === 'float')
value = parseFloat(value);
ami.settings.set(elt.name, value);
};
if(elt.options !== undefined) {
for(var val in elt.options) {
var optionInfo = elt.options[val];
var option = document.createElement("option");
option.value = optionInfo[0];
option.innerHTML = optionInfo[1];
input.appendChild(option);
}
}
ami.settings.watch(elt.name, function(value) {
input.value = value;
});
cell.appendChild(input);
break;
case "checkbox":
input = document.createElement("input");
input.id = 'chatSetting-' + elt.id;
input.type = 'checkbox';
input.onchange = function(e) {
ami.settings.set(elt.name, this.checked);
};
ami.settings.watch(elt.name, function(value) {
input.checked = value;
});
cell.appendChild(input);
break;
}
if(input !== null) {
if(elt.load) {
elt.load.call(this, input);
input.value = ami.settings.get(elt.name);
}
++cnt;
}
});
UI.bbcode.forEach(function (elt, i, arr) {
if(elt.enableSetting) {
var row = table.insertRow(cnt);
row.className = cnt % 2 == 0 ? "rowOdd" : "rowEven";
row.setAttribute("name", "||" + elt["tag"]);
var cell = row.insertCell(0);
cell.innerHTML = "enable " + elt["tag"];
cell = row.insertCell(1);
cell.className = "setting";
input = document.createElement("input");
input.setAttribute("type", "checkbox");
input.checked = ami.settings.get(elt.enableSetting);
input.onchange = function (e) {
ami.settings.set(elt.enableSetting, this.checked);
};
cell.appendChild(input);
++cnt;
}
if(elt.persistSetting) {
var row = table.insertRow(cnt);
row.className = cnt % 2 == 0 ? "rowOdd" : "rowEven";
row.setAttribute("name", ";;" + elt["tag"]);
var cell = row.insertCell(0);
cell.innerHTML = "persist " + elt["tag"];
cell = row.insertCell(1);
cell.className = "setting";
input = document.createElement("input");
input.setAttribute("type", "checkbox");
input.checked = ami.settings.get(elt.persistSetting);
input.onchange = function(e) {
var settingValue = this.checked;
if(!elt.persistClassName && !elt.persistStyleValue)
settingValue = settingValue ? '' : null;
ami.settings.set(elt.persistSetting, settingValue);
};
ami.settings.watch(elt.persistSetting, function(value) {
if(elt.persistClassName)
ami.chat.inputBox.setClassName(elt.persistClassName, value);
if(elt.persistStyleAttr)
ami.chat.inputBox.setStyleValue(elt.persistStyleAttr, elt.persistStyleValue || value, !!value, !!elt.persistStyleSingle);
});
cell.appendChild(input);
++cnt;
}
});
// move into ctx when the sidebars code doesn't suck
ami.settings.watch('soundMute', function(value) {
if(!value && (!ami.sound.hasPacks() || !ami.sound.hasSoundLibrary())) {
ami.sound.clearPacks();
ami.sound.clearSoundLibrary();
futami.getJson('sounds2', function(sounds) {
if(typeof sounds !== 'object')
return;
if(Array.isArray(sounds.library))
for(var i in sounds.library)
ami.sound.registerLibrarySound(sounds.library[i]);
if(Array.isArray(sounds.packs)) {
for(var i in sounds.packs)
ami.sound.registerSoundPack(sounds.packs[i]);
if(sounds.packs.length > 0 && !ami.settings.has('soundPack'))
ami.settings.set('soundPack', sounds.packs[0].name);
}
var soundPackSetting = $i('chatSetting-spack');
if(soundPackSetting) {
while(soundPackSetting.options.length > 0)
soundPackSetting.options.remove(0);
ami.sound.forEachPack(function(pack) {
var opt = document.createElement('option');
opt.value = pack.getName();
opt.innerHTML = pack.getTitle();
soundPackSetting.appendChild(opt);
if(ami.settings.get('soundPack') === '')
ami.settings.set('soundPack', pack.getName());
});
soundPackSetting.value = ami.settings.get('soundPack');
}
});
}
ami.sound.setMuted(value);
});
for(var i in bbcodes)
(function(bbcodeInfo) {
if(!bbcodeInfo.button)
return;
var langKey = typeof bbcodeInfo.button === 'string' ? null : bbcodeInfo.tag;
var button = ami.chat.optionButtons.create(bbcodeInfo.tag, bbcodeInfo.tag, langKey, bbcodeInfo.buttonStyle, function(buttonInfo, ev) {
if(!bbcodeInfo.arg) {
Chat.InsertBBCode(bbcodeInfo.tag);
return;
}
if(bbcodeInfo.bhandle) {
bbcodeInfo.bhandle.call(buttonInfo, buttonInfo, ev);
return;
}
var tagArg = prompt(buttonInfo.bprompt && AmiStrings.hasMenuString(buttonInfo.bprompt) ? AmiStrings.getMenuString(buttonInfo.bprompt) : AmiStrings.getMenuString('bprompt'));
if(tagArg === null || tagArg === undefined)
return;
Chat.InsertBBCode(bbcodeInfo.tag, tagArg);
});
if(bbcodeInfo.enableSetting)
ami.settings.watch(bbcodeInfo.enableSetting, function(value) {
button[value ? 'show' : 'hide']();
});
})(bbcodes[i]);
ami.chat.inputBox.watch('tab', function(ev) {
if('preventDefault' in ev)
ev.preventDefault();
if(ami.chat.inputBox.isEmpty())
return;
var cursorWord = ami.chat.inputBox.getWordAtCursor(),
snippet = cursorWord.word.toLowerCase(),
insertText = undefined;
if(snippet.length > 0) {
if(snippet.indexOf(':') === 0) {
var emoteRank = 0;
if(UserContext.self !== undefined)
emoteRank = parseInt(UserContext.self.perms[0]);
var emotes = ami.emoticons.findByName(emoteRank, snippet.substring(1), true);
if(emotes.length > 0)
insertText = ':' + emotes[0] + ':';
} else {
var findUserNames = [];
for(var i in UserContext.users) {
var findUserName = UserContext.users[i].username;
if(findUserName.toLowerCase().indexOf(snippet) >= 0)
findUserNames.push(findUserName);
}
if(findUserNames.length === 1)
insertText = findUserNames[0];
}
}
if(insertText !== undefined)
ami.chat.inputBox.insertAt(cursorWord.start, insertText, -snippet.length);
});
ami.chat.inputBox.watch('enter', function(ev) {
if('preventDefault' in ev)
ev.preventDefault();
var text = ami.chat.inputBox.getValue();
text = text.replace(/\t/g, ' ');
if(text.trim().charAt(0) != '/') {
for(var i in bbcodes) {
var bbcode = bbcodes[i];
if(bbcode.persistSetting) {
var settingValue = ami.settings.get(bbcode.persistSetting);
if(settingValue) {
if(typeof settingValue === 'boolean')
text = '[' + bbcode.tag + ']' + text + '[/' + bbcode.tag + ']';
else
text = '[' + bbcode.tag + '=' + settingValue + ']' + text + '[/' + bbcode.tag + ']';
}
}
}
}
if(text.trim() !== '')
ami.sockChat.sendMessage(text);
ami.chat.inputBox.clear();
ami.chat.inputBox.focus();
});
UI.RenderLanguage();
AmiEEPROM.init(function(success) {
if(!success)
return;
// set up old eeprom code
eepromInitialise(auth);
});
};
Chat.InsertBBCode = function (tag, arg) {
if (arg === void 0) { arg = null; }
var bbcode = null;
for(var i in bbcodes) {
var info = bbcodes[i];
if(info.tag === tag) {
bbcode = info;
break;
}
}
if(bbcode && bbcode.persistSetting) {
var persistValue = ami.settings.get(bbcode.persistSetting);
if(persistValue !== null && persistValue !== false) {
ami.settings.set(bbcode.persistSetting, arg);
ami.chat.inputBox.focus();
return;
}
}
if (arg == null)
ami.chat.inputBox.insertAround('[' + tag + ']', '[/' + tag + ']');
else
ami.chat.inputBox.insertAround('[' + tag + '=' + arg + ']', '[/' + tag + ']');
ami.chat.inputBox.focus();
};
return Chat;
})();

73
src/ami.js/ts_utils.js Normal file
View file

@ -0,0 +1,73 @@
#include ts_20_ui.js
var Utils = (function () {
function Utils() {
}
Utils.replaceAll = function (haystack, needle, replace, ignore) {
if (ignore === void 0) { ignore = false; }
return haystack.replace(new RegExp(needle.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, "\\$&"), (ignore ? "gi" : "g")), (typeof (replace) == "string") ? replace.replace(/\$/g, "$$$$") : replace);
};
Utils.Sanitize = function (str) {
return Utils.replaceAll(Utils.replaceAll(Utils.replaceAll(str, ">", "&gt;"), "<", "&lt;"), "\n", " <br /> ");
};
Utils.StripCharacters = function (str, chars) {
if (chars != "") {
for (var i = 0; i < chars.length; i++)
str = Utils.replaceAll(str, chars[i], "");
}
return str;
};
Utils.EmbedImage = function (link) {
var id = link.parentElement.title;
var holder = link.parentElement.getElementsByTagName("span")[0];
var imglink = holder.getElementsByTagName("a")[0];
imglink.innerHTML = holder.title == "link" ? "<img src='" + id + "' alt='userimg' class='insertImage' />" : id;
link.innerHTML = holder.title == "link" ? "Remove" : "Embed";
holder.title = holder.title == "link" ? "image" : "link";
link.classList[holder.title == 'link' ? 'add' : 'remove']('js-embed-btn');
link.classList[holder.title == 'link' ? 'remove' : 'add']('js-unembed-btn');
};
Utils.EmbedAudio = function (link) {
var id = link.parentElement.title;
var holder = link.parentElement.getElementsByTagName("span")[0];
holder.innerHTML = holder.title == "link" ? "<audio src='" + id + "' class='insertAudio' controls></audio>" : "<a href='"+ id +"' onclick='window.open(this.href);return false;'>"+ id +"</a>";
link.innerHTML = holder.title == "link" ? "Remove" : "Embed";
holder.title = holder.title == "link" ? "image" : "link";
link.classList[holder.title == 'link' ? 'add' : 'remove']('js-embed-btn');
link.classList[holder.title == 'link' ? 'remove' : 'add']('js-unembed-btn');
};
Utils.EmbedVideo = function (link) {
var id = link.parentElement.title;
var holder = link.parentElement.getElementsByTagName("span")[0];
holder.innerHTML = holder.title == "link" ? "<video src='" + id + "' style='max-width: 480px; max-height: 360px;' controls class='insertVideo'></video>" : "<a href='"+ id +"' onclick='window.open(this.href);return false;'>"+ id +"</a>";
link.innerHTML = holder.title == "link" ? "Remove" : "Embed";
holder.title = holder.title == "link" ? "image" : "link";
link.classList[holder.title == 'link' ? 'add' : 'remove']('js-embed-btn');
link.classList[holder.title == 'link' ? 'remove' : 'add']('js-unembed-btn');
};
Utils.ToggleSpoiler = function(element) {
var container = element.parentElement,
target = container.getElementsByTagName("span")[0];
if(container.dataset.revealed === 'yes') {
container.dataset.revealed = 'no';
target.textContent = '*** HIDDEN ***';
element.textContent = 'Reveal';
} else {
container.dataset.revealed = 'yes';
target.textContent = container.dataset.shit;
element.textContent = 'Hide';
}
}
Utils.SanitizeRegex = function (input) {
var out = "";
for (var i = 0; i < input.length; i++) {
var cc = input.charCodeAt(i);
if (!((cc > 47 && cc < 58) || (cc > 64 && cc < 91) || (cc > 96 && cc < 123)))
out += "\\";
out += input.charAt(i);
}
return out;
};
return Utils;
})();

130
src/ami.js/txtrigs.js Normal file
View file

@ -0,0 +1,130 @@
var AmiTextTrigger = function(info) {
var type = info.type,
match = info.match;
for(var i in match) {
match[i] = match[i].split(';');
for(var j in match[i])
match[i][j] = match[i][j].trim().split(':');
}
var pub = {
getType: function() { return type; },
isSoundType: function() { return type === 'sound'; },
isAliasType: function() { return type === 'alias'; },
isMatch: function(matchText) {
for(var i in match) {
var out = (function(filters, text) {
var result = false;
for(var j in filters) {
var filter = filters[j];
switch(filter[0]) {
case 'lc':
text = text.toLowerCase();
break;
case 'is':
if(text === filter.slice(1).join(':'))
result = true;
break;
case 'starts':
if(text.indexOf(filter.slice(1).join(':')) === 0)
result = true;
break;
case 'has':
if(text.indexOf(filter.slice(1).join(':')) >= 0)
result = true;
break;
case 'hasnot':
if(text.indexOf(filter.slice(1).join(':')) >= 0)
result = false;
break;
default:
console.log('Unknown filter encountered: ' + filter.join(':'));
break;
}
}
return result;
})(match[i], matchText);
if(out) return true;
}
return false;
},
};
if(type === 'sound') {
var volume = info.volume || 1.0,
rate = info.rate || 1.0,
names = info.sounds || [];
pub.getVolume = function() { return volume; };
pub.getRate = function() {
if(rate === 'rng')
return 1.8 - (Math.random() * 1.5);
return rate;
};
pub.getSoundNames = function() { return names; };
pub.getRandomSoundName = function() { return names[Math.floor(Math.random() * names.length)]; };
} else if(type === 'alias') {
var aliasFor = info.for || [];
pub.getFor = function() { return aliasFor; };
} else
throw 'Unsupported trigger type.';
return pub;
};
var AmiTextTriggers = function() {
var triggers = [];
var addTrigger = function(triggerInfo) {
if(triggerInfo === null || typeof triggerInfo.type !== 'string')
throw 'triggerInfo is not a valid trigger';
triggers.push(new AmiTextTrigger(triggerInfo));
};
var getTrigger = function(text, returnAlias) {
for(var i in triggers) {
var trigger = triggers[i];
if(trigger.isMatch(text)) {
if(trigger.isAliasType() && !returnAlias) {
var aliasFor = trigger.getFor();
trigger = getTrigger(aliasFor[Math.floor(Math.random() * aliasFor.length)]);
}
return trigger;
}
}
throw 'no trigger that matches this text';
};
return {
addTrigger: addTrigger,
addTriggers: function(triggerInfos) {
for(var i in triggerInfos)
try {
addTrigger(triggerInfos[i]);
} catch(ex) {
console.log(ex);
}
},
clearTriggers: function() {
triggers = [];
},
hasTriggers: function() {
return triggers.length > 0;
},
getTrigger: getTrigger,
};
};

145
src/ami.js/users.js Normal file
View file

@ -0,0 +1,145 @@
var AmiUserPerms = function(perms) {
var rank = parseInt(perms[0] || '0');
var values = {
'baka': perms[1] !== '0',
'ban': 'baka',
'kick': 'baka',
'view-logs': perms[2] !== '0',
'set-nick-name': perms[3] !== '0',
'create-channel': perms[4] !== '0',
'create-temp-channel': perms[4] === '1',
'create-perm-channel': perms[4] === '2',
};
var can = function(perm) {
perm = values[perm];
if(typeof perm === 'string')
return can(perm);
return perm;
};
return {
getRank: function() { return rank; },
can: can,
compare: function(other) {
var diff = [];
for(var perm in values)
if(can(perm) !== other.can(perm))
diff.push(perm);
return diff;
},
};
};
var AmiUser = function(args) {
var id = args.id,
hidden = !!args.hidden,
name = undefined,
colour = undefined,
perms = undefined;
var awayStatus = undefined,
isNickName = false;
var afkPfx = '&lt;', afkSfx = '&gt;_', nickPfx = '~';
var setName = function(newName) {
if(newName.indexOf(afkPfx) === 0) {
var tagEnd = newName.indexOf(afkSfx);
if(tagEnd > afkSfx.length) {
awayStatus = newName.substring(afkPfx.length, tagEnd);
newName = newName.substring(tagEnd + afkSfx.length);
} else awayStatus = undefined;
} else awayStatus = undefined;
isNickName = newName.indexOf(nickPfx) === 0;
if(isNickName)
newName = newName.substring(nickPfx.length);
name = newName;
};
var setColour = function(newColour) {
colour = newColour;
};
var setPerms = function(newPerms) {
perms = new AmiUserPerms(newPerms);
};
setName(args.name);
setColour(args.colour);
setPerms(args.perms);
var getName = function() { return name; };
var getFullName = function() {
var pfx = '';
if(awayStatus !== undefined)
pfx += afkPfx + awayStatus + afkSfx;
if(isNickName)
pfx += nickPfx;
return pfx + name;
};
var getColour = function() { return colour; };
var getPerms = function() { return perms; };
var isHidden = function() { return hidden; };
return {
getId: function() { return id; },
getName: getName,
getFullName: getFullName,
isAway: function() { return awayStatus !== undefined; },
getAwayStatus: function() { return awayStatus; },
hasNickName: function() { return isNickName; },
setName: setName,
getColour: getColour,
setColour: setColour,
getPerms: getPerms,
setPerms: setPerms,
isHidden: isHidden,
compare: function(other) {
var diff = { diff: [], a: {}, b: {} };
var idA = getId(),
idB = 'id' in other ? other.id : other.getId();
if(idA !== idB) {
diff.diff.push('id');
diff.a.id = idA;
diff.b.id = idB;
}
var nameA = getFullName(),
nameB = 'name' in other ? other.name : other.getFullName();
if(nameA !== nameB) {
diff.diff.push('name');
diff.a.name = nameA;
diff.b.name = nameB;
}
var colourA = getColour(),
colourB = 'colour' in other ? other.colour : other.getColour();
if(colourA !== colourB) {
diff.diff.push('colour');
diff.a.colour = colourA;
diff.b.colour = colourB;
}
var permsA = getPerms(),
permsB = 'perms' in other ? new AmiUserPerms(other.perms) : other.getPerms(),
permsDiff = permsA.compare(permsB);
if(permsDiff.length > 0) {
diff.diff.push('perms');
diff.a.perms = permsA;
diff.b.perms = permsB;
}
return diff;
},
};
};

163
src/ami.js/utility.js Normal file
View file

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

85
src/ami.js/watcher.js Normal file
View file

@ -0,0 +1,85 @@
#include utility.js
var AmiWatcher = function() {
var watchers = [];
return {
watch: function(watcher, thisArg, args) {
if(typeof watcher !== 'function')
throw 'watcher must be a function';
if(watchers.indexOf(watcher) >= 0)
return;
watchers.push(watcher);
if(thisArg !== undefined) {
if(!Array.isArray(args)) {
if(args !== undefined)
args = [args];
else args = [];
}
// initial call
args.push(true);
watcher.apply(thisArg, args);
}
},
unwatch: function(watcher) {
$ari(watchers, watcher);
},
call: function(thisArg, args) {
if(!Array.isArray(args)) {
if(args !== undefined)
args = [args];
else args = [];
}
args.push(false);
for(var i in watchers)
watchers[i].apply(thisArg, args);
},
};
};
var AmiWatcherCollection = function() {
var collection = new Map;
var watch = function(name, watcher, thisArg, args) {
var watchers = collection.get(name);
if(watchers === undefined)
throw 'undefined watcher name';
watchers.watch(watcher, thisArg, args);
};
var unwatch = function(name, watcher) {
var watchers = collection.get(name);
if(watchers === undefined)
throw 'undefined watcher name';
watchers.unwatch(watcher);
};
return {
define: function(names) {
if(!Array.isArray(names))
names = [names];
for(var i in names)
collection.set(names[i], new AmiWatcher);
},
call: function(name, thisArg, args) {
var watchers = collection.get(name);
if(watchers === undefined)
throw 'undefined watcher name';
watchers.call(thisArg, args);
},
watch: watch,
unwatch: unwatch,
proxy: function(obj) {
obj.watch = function(name, watcher) {
watch(name, watcher);
};
obj.unwatch = unwatch;
},
};
};

169
src/ami.js/z_eepromv1.js Normal file
View file

@ -0,0 +1,169 @@
#include common.js
#include utility.js
var eepromAppl = 1,
eepromSupportImageEmbed = true,
eepromSupportAudioEmbed = true,
eepromSupportVideoEmbed = true;
var eepromInitialise = function(auth) {
window.eepromHistory = {};
window.eepromClient = new EEPROM(eepromAppl, futami.get('eeprom'), auth.getHttpAuth());
window.eepromUploadsTable = $i('uploadHistory');
$i('uploadSelector').onchange = function() {
for(var i = 0; i < this.files.length; ++i)
eepromFileUpload(this.files[i]);
};
$i('uploadButton').onclick = function() {
$i('uploadSelector').click();
};
ami.chat.inputBox.watch('paste', function(ev) {
if(ev.clipboardData && ev.clipboardData.files.length > 0) {
if('preventDefault' in ev)
ev.preventDefault();
for(var i = 0; i < ev.clipboardData.files.length; ++i)
eepromFileUpload(ev.clipboardData.files[i]);
}
});
if((typeof document.body.ondrop).toLowerCase() !== 'undefined') {
document.body.ondragenter = function(ev) {
ev.preventDefault();
ev.stopPropagation();
};
document.body.ondragover = function(ev) {
ev.preventDefault();
ev.stopPropagation();
};
document.body.ondragleave = function(ev) {
ev.preventDefault();
ev.stopPropagation();
};
document.body.ondrop = function(ev) {
ev.preventDefault();
ev.stopPropagation();
if(ev.dataTransfer && ev.dataTransfer.files.length > 0)
for(var i = 0; i < ev.dataTransfer.files.length; ++i)
eepromFileUpload(ev.dataTransfer.files[i]);
};
}
};
var eepromFileUpload = function(file) {
if(!window.eepromUploadsTable || !window.eepromClient) {
console.error('Upload history or EEPROM client is missing???');
return;
}
var uploadEntryWrap = window.eepromUploadsTable.insertRow(0),
uploadEntry = uploadEntryWrap.insertCell(0);
uploadEntryWrap.className = (window.eepromUploadsTable.rows.length % 2) ? 'rowOdd' : 'rowEven';
var name = document.createElement('a');
name.target = '_blank';
name.style.wordWrap = 'normal';
name.style.wordBreak = 'break-word';
name.style.display = 'block';
name.style.margin = '2px 10px';
name.style.textDecoration = 'none !important';
name.textContent = file.name;
name.title = 'SHIFT+Click to delete file';
uploadEntry.appendChild(name);
var prog = document.createElement('div'),
progBar = document.createElement('div');
prog.style.border = '1px solid #808080';
prog.style.background = '#000';
prog.style.padding = '0';
prog.style.margin = '2px 10px';
progBar.style.background = '#808080';
progBar.style.width = '0%';
progBar.style.height = '12px';
progBar.style.padding = '0';
progBar.style.border = '1px solid #000';
progBar.style.boxSizing = 'border-box';
prog.appendChild(progBar);
uploadEntry.appendChild(prog);
var uploadTask = window.eepromClient.createUpload(file);
uploadTask.onProgress = function(progressInfo) {
progBar.style.width = progressInfo.progress.toString() + '%';
};
uploadTask.onFailure = function(errorInfo) {
if(!errorInfo.userAborted) {
var errorText = 'Was unable to upload file.';
switch(errorInfo.error) {
case EEPROM.ERR_INVALID:
errorText = 'Upload request was invalid.';
break;
case EEPROM.ERR_AUTH:
errorText = 'Upload authentication failed, refresh and try again.';
break;
case EEPROM.ERR_ACCESS:
errorText = 'You\'re not allowed to upload files.';
break;
case EEPROM.ERR_GONE:
errorText = 'Upload client has a configuration error or the server is gone.';
break;
case EEPROM.ERR_DMCA:
errorText = 'This file has been uploaded before and was removed for copyright reasons, you cannot upload this file.';
break;
case EEPROM.ERR_SERVER:
errorText = 'Upload server returned a critical error, try again later.';
break;
case EEPROM.ERR_SIZE:
if(errorInfo.maxSize < 1)
errorText = 'Selected file is too large.';
else {
var _t = ['bytes', 'KB', 'MB', 'GB', 'TB'],
_i = parseInt(Math.floor(Math.log(errorInfo.maxSize) / Math.log(1024))),
_s = Math.round(errorInfo.maxSize / Math.pow(1024, _i), 2);
errorText = 'Upload may not be larger than %1 %2.'.replace('%1', _s).replace('%2', _t[_i]);
}
break;
}
alert(errorText);
}
uploadHistory.deleteRow(uploadEntryWrap.rowIndex);
};
name.onclick = function(ev) {
if(ev.shiftKey) {
ev.preventDefault();
var fileInfo = window.eepromHistory[name.getAttribute('data-eeprom-id') || ''];
if(fileInfo) {
window.eepromClient.deleteUpload(fileInfo).start();
uploadHistory.deleteRow(uploadEntryWrap.rowIndex);
} else uploadTask.abort();
}
};
uploadTask.onComplete = function(fileInfo) {
window.eepromHistory[fileInfo.id] = fileInfo;
name.style.textDecoration = null;
name.setAttribute('data-eeprom-id', fileInfo.id);
name.href = fileInfo.url;
uploadEntry.removeChild(prog);
var insertText = location.protocol + fileInfo.url;
if(eepromSupportImageEmbed && fileInfo.type.indexOf('image/') === 0)
insertText = '[img]' + fileInfo.url + '[/img]';
else if(eepromSupportAudioEmbed && (fileInfo.type === 'application/x-font-gdos' || fileInfo.type.indexOf('audio/') === 0))
insertText = '[audio]' + fileInfo.url + '[/audio]';
else if(eepromSupportVideoEmbed && fileInfo.type.indexOf('video/') === 0)
insertText = '[video]' + fileInfo.url + '[/video]';
ami.chat.inputBox.insert(insertText);
};
uploadTask.start();
};

108
src/assproc.js Normal file
View file

@ -0,0 +1,108 @@
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const utils = require('./utils.js');
exports.process = async function(root, options) {
const macroPrefix = options.prefix || '#';
const entryPoint = options.entry || '';
root = fs.realpathSync(root);
const included = [];
const processFile = async function(fileName) {
const fullPath = path.join(root, fileName);
if(included.includes(fullPath))
return '';
included.push(fullPath);
if(!fullPath.startsWith(root))
return '/* *** INVALID PATH: ' + fullPath + ' */';
if(!fs.existsSync(fullPath))
return '/* *** FILE NOT FOUND: ' + fullPath + ' */';
const lines = readline.createInterface({
input: fs.createReadStream(fullPath),
crlfDelay: Infinity,
});
let output = '';
let lastWasEmpty = false;
if(options.showPath)
output += "/* *** PATH: " + fullPath + " */\n";
for await(const line of lines) {
const lineTrimmed = utils.trim(line);
if(lineTrimmed === '')
continue;
if(line.startsWith(macroPrefix)) {
const args = lineTrimmed.split(' ');
const macro = utils.trim(utils.trimStart(args.shift(), macroPrefix));
switch(macro) {
case 'comment':
break;
case 'include': {
const includePath = utils.trimEnd(args.join(' '), ';');
output += utils.trim(await processFile(includePath));
output += "\n";
break;
}
case 'buildvars':
if(typeof options.buildVars === 'object') {
const bvTarget = options.buildVarsTarget || 'window';
const bvProps = [];
for(const bvName in options.buildVars)
bvProps.push(`${bvName}: { value: ${JSON.stringify(options.buildVars[bvName])}, writable: false }`);
if(Object.keys(bvProps).length > 0)
output += `Object.defineProperties(${bvTarget}, { ${bvProps.join(', ')} });\n`;
}
break;
default:
output += line;
output += "\n";
break;
}
} else {
output += line;
output += "\n";
}
}
return output;
};
return await processFile(entryPoint);
};
exports.housekeep = function(assetsPath) {
const files = fs.readdirSync(assetsPath).map(fileName => {
const stats = fs.statSync(path.join(assetsPath, fileName));
return {
name: fileName,
lastMod: stats.mtimeMs,
};
}).sort((a, b) => b.lastMod - a.lastMod).map(info => info.name);
const regex = /^(.+)[\-\.]([a-f0-9]+)\.(.+)$/i;
const counts = {};
for(const fileName of files) {
const match = fileName.match(regex);
if(match) {
const name = match[1] + '-' + match[3];
counts[name] = (counts[name] || 0) + 1;
if(counts[name] > 5)
fs.unlinkSync(path.join(assetsPath, fileName));
} else console.log(`Encountered file name in assets folder with unexpected format: ${fileName}`);
}
};

35
src/utils.js Normal file
View file

@ -0,0 +1,35 @@
const crypto = require('crypto');
exports.strtr = (str, replacements) => str.toString().replace(
/{([^}]+)}/g, (match, key) => replacements[key] || match
);
const trim = function(str, chars, flags) {
if(chars === undefined)
chars = " \n\r\t\v\0";
let start = 0,
end = str.length;
if(flags & 0x01)
while(start < end && chars.indexOf(str[start]) >= 0)
++start;
if(flags & 0x02)
while(end > start && chars.indexOf(str[end - 1]) >= 0)
--end;
return (start > 0 || end < str.length)
? str.substring(start, end)
: str;
};
exports.trimStart = (str, chars) => trim(str, chars, 0x01);
exports.trimEnd = (str, chars) => trim(str, chars, 0x02);
exports.trim = (str, chars) => trim(str, chars, 0x03);
exports.shortHash = function(text) {
const hash = crypto.createHash('sha256');
hash.update(text);
return hash.digest('hex').substring(0, 8);
};