commit af337ab2cfedd25a7e948a5c42d553e510c18723 Author: flashwave Date: Thu Jan 18 19:51:52 2024 +0000 Imported Ami into own repository. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f5c8df --- /dev/null +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b6f5ec --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Ami + +Chat client forked from the original Sock Chat client. Maintained for compatibility with Firefox 10+ and similar browsers. diff --git a/assets/ami buttons sprite.psd b/assets/ami buttons sprite.psd new file mode 100644 index 0000000..c3e6549 Binary files /dev/null and b/assets/ami buttons sprite.psd differ diff --git a/build.js b/build.js new file mode 100644 index 0000000..0347006 --- /dev/null +++ b/build.js @@ -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); +})(); diff --git a/config/config.example.json b/config/config.example.json new file mode 100644 index 0000000..dabc3fe --- /dev/null +++ b/config/config.example.json @@ -0,0 +1,6 @@ +{ + "title": "Flashii Chat", + "common_url": "//futami.flashii.net/common.json", + "modern_url": "//chat.flashii.net", + "compat_url": "//sockchat.flashii.net" +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8c3bf9b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1372 @@ +{ + "name": "sockchat.edgii.net", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "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" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz", + "integrity": "sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@swc/core": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.104.tgz", + "integrity": "sha512-9LWH/qzR/Pmyco+XwPiPfz59T1sryI7o5dmqb593MfCkaX5Fzl9KhwQTI47i21/bXYuCdfa9ySZuVkzXMirYxA==", + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.104", + "@swc/core-darwin-x64": "1.3.104", + "@swc/core-linux-arm-gnueabihf": "1.3.104", + "@swc/core-linux-arm64-gnu": "1.3.104", + "@swc/core-linux-arm64-musl": "1.3.104", + "@swc/core-linux-x64-gnu": "1.3.104", + "@swc/core-linux-x64-musl": "1.3.104", + "@swc/core-win32-arm64-msvc": "1.3.104", + "@swc/core-win32-ia32-msvc": "1.3.104", + "@swc/core-win32-x64-msvc": "1.3.104" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.104.tgz", + "integrity": "sha512-rCnVj8x3kn6s914Adddu+zROHUn6mUEMkNKUckofs3W9OthNlZXJA3C5bS2MMTRFXCWamJ0Zmh6INFpz+f4Tfg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.104.tgz", + "integrity": "sha512-LBCWGTYkn1UjyxrmcLS3vZgtCDVhwxsQMV7jz5duc7Gas8SRWh6ZYqvUkjlXMDX1yx0uvzHrkaRw445+zDRj7Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.104.tgz", + "integrity": "sha512-iFbsWcx0TKHWnFBNCuUstYqRtfkyBx7FKv5To1Hx14EMuvvoCD/qUoJEiNfDQN5n/xU9g5xq4RdbjEWCFLhAbA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.104.tgz", + "integrity": "sha512-1BIIp+nUPrRHHaJ35YJqrwXPwYSITp5robqqjyTwoKGw2kq0x+A964kpWul6v0d7A9Ial8fyH4m13eSWBodD2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.104.tgz", + "integrity": "sha512-IyDNkzpKwvLqmRwTW+s8f8OsOSSj1N6juZKbvNHpZRfWZkz3T70q3vJlDBWQwy8z8cm7ckd7YUT3eKcSBPPowg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.104.tgz", + "integrity": "sha512-MfX/wiRdTjE5uXHTDnaX69xI4UBfxIhcxbVlMj//N+7AX/G2pl2UFityfVMU2HpM12BRckrCxVI8F/Zy3DZkYQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.104.tgz", + "integrity": "sha512-5yeILaxA31gGEmquErO8yxlq1xu0XVt+fz5mbbKXKZMRRILxYxNzAGb5mzV41r0oHz6Vhv4AXX/WMCmeWl+HkQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.104.tgz", + "integrity": "sha512-rwcImsYnWDWGmeESG0XdGGOql5s3cG5wA8C4hHHKdH76zamPfDKKQFBsjmoNi0f1IsxaI9AJPeOmD4bAhT1ZoQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.104.tgz", + "integrity": "sha512-ICDA+CJLYC7NkePnrbh/MvXwDQfy3rZSFgrVdrqRosv9DKHdFjYDnA9++7ozjrIdFdBrFW2NR7pyUcidlwhNzA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.104", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.104.tgz", + "integrity": "sha512-fZJ1Ju62U4lMZVU+nHxLkFNcu0hG5Y0Yj/5zjrlbuX5N8J5eDndWAFsVnQhxRTZqKhZB53pvWRQs5FItSDqgXg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==" + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==" + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.17", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", + "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.22.2", + "caniuse-lite": "^1.0.30001578", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz", + "integrity": "sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz", + "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==", + "dependencies": { + "cssnano-preset-default": "^6.0.3", + "lilconfig": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz", + "integrity": "sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==", + "dependencies": { + "css-declaration-sorter": "^7.1.1", + "cssnano-utils": "^4.0.1", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.0.2", + "postcss-convert-values": "^6.0.2", + "postcss-discard-comments": "^6.0.1", + "postcss-discard-duplicates": "^6.0.1", + "postcss-discard-empty": "^6.0.1", + "postcss-discard-overridden": "^6.0.1", + "postcss-merge-longhand": "^6.0.2", + "postcss-merge-rules": "^6.0.3", + "postcss-minify-font-values": "^6.0.1", + "postcss-minify-gradients": "^6.0.1", + "postcss-minify-params": "^6.0.2", + "postcss-minify-selectors": "^6.0.2", + "postcss-normalize-charset": "^6.0.1", + "postcss-normalize-display-values": "^6.0.1", + "postcss-normalize-positions": "^6.0.1", + "postcss-normalize-repeat-style": "^6.0.1", + "postcss-normalize-string": "^6.0.1", + "postcss-normalize-timing-functions": "^6.0.1", + "postcss-normalize-unicode": "^6.0.2", + "postcss-normalize-url": "^6.0.1", + "postcss-normalize-whitespace": "^6.0.1", + "postcss-ordered-values": "^6.0.1", + "postcss-reduce-initial": "^6.0.2", + "postcss-reduce-transforms": "^6.0.1", + "postcss-svgo": "^6.0.2", + "postcss-unique-selectors": "^6.0.2" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz", + "integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.638", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.638.tgz", + "integrity": "sha512-gpmbAG2LbfPKcDaL5m9IKutKjUx4ZRkvGNkgL/8nKqxkXsBVYykVULboWlqCrHsh3razucgDJDuKoWJmGPdItA==" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/postcss": { + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz", + "integrity": "sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==", + "dependencies": { + "browserslist": "^4.22.2", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz", + "integrity": "sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==", + "dependencies": { + "browserslist": "^4.22.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz", + "integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz", + "integrity": "sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz", + "integrity": "sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz", + "integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz", + "integrity": "sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.0.2" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz", + "integrity": "sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==", + "dependencies": { + "browserslist": "^4.22.2", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.1", + "postcss-selector-parser": "^6.0.15" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz", + "integrity": "sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz", + "integrity": "sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^4.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz", + "integrity": "sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==", + "dependencies": { + "browserslist": "^4.22.2", + "cssnano-utils": "^4.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz", + "integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==", + "dependencies": { + "postcss-selector-parser": "^6.0.15" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", + "integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz", + "integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz", + "integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz", + "integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz", + "integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz", + "integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz", + "integrity": "sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==", + "dependencies": { + "browserslist": "^4.22.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz", + "integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz", + "integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz", + "integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==", + "dependencies": { + "cssnano-utils": "^4.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz", + "integrity": "sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==", + "dependencies": { + "browserslist": "^4.22.2", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz", + "integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz", + "integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz", + "integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.15" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stylehacks": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz", + "integrity": "sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==", + "dependencies": { + "browserslist": "^4.22.2", + "postcss-selector-parser": "^6.0.15" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/svgo": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", + "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/terser": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..bfdb1d5 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..62a03e7 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/images/alert.png b/public/images/alert.png new file mode 100644 index 0000000..4f9ddc6 Binary files /dev/null and b/public/images/alert.png differ diff --git a/public/images/clouds-700000.jpg b/public/images/clouds-700000.jpg new file mode 100644 index 0000000..16d611f Binary files /dev/null and b/public/images/clouds-700000.jpg differ diff --git a/public/images/clouds-8559a5.jpg b/public/images/clouds-8559a5.jpg new file mode 100644 index 0000000..4fe2ef1 Binary files /dev/null and b/public/images/clouds-8559a5.jpg differ diff --git a/public/images/clouds-legacy.jpg b/public/images/clouds-legacy.jpg new file mode 100644 index 0000000..153b88f Binary files /dev/null and b/public/images/clouds-legacy.jpg differ diff --git a/public/images/fade-purple.png b/public/images/fade-purple.png new file mode 100644 index 0000000..46f6669 Binary files /dev/null and b/public/images/fade-purple.png differ diff --git a/public/images/load.gif b/public/images/load.gif new file mode 100644 index 0000000..0d9f9fc Binary files /dev/null and b/public/images/load.gif differ diff --git a/public/images/loading-sprite-link.png b/public/images/loading-sprite-link.png new file mode 100644 index 0000000..f58b6ff Binary files /dev/null and b/public/images/loading-sprite-link.png differ diff --git a/public/images/loading-sprite.png b/public/images/loading-sprite.png new file mode 100644 index 0000000..e474d4d Binary files /dev/null and b/public/images/loading-sprite.png differ diff --git a/public/images/sprite.png b/public/images/sprite.png new file mode 100644 index 0000000..5d95d08 Binary files /dev/null and b/public/images/sprite.png differ diff --git a/public/picker.css b/public/picker.css new file mode 100644 index 0000000..6b14151 --- /dev/null +++ b/public/picker.css @@ -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; +} diff --git a/public/picker.js b/public/picker.js new file mode 100644 index 0000000..1f81e85 --- /dev/null +++ b/public/picker.js @@ -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; +}; diff --git a/src/ami.css/animations.css b/src/ami.css/animations.css new file mode 100644 index 0000000..734eac0 --- /dev/null +++ b/src/ami.css/animations.css @@ -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; } +} diff --git a/src/ami.css/domaintrans.css b/src/ami.css/domaintrans.css new file mode 100644 index 0000000..21caede --- /dev/null +++ b/src/ami.css/domaintrans.css @@ -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); +} diff --git a/src/ami.css/main.css b/src/ami.css/main.css new file mode 100644 index 0000000..80f140f --- /dev/null +++ b/src/ami.css/main.css @@ -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', 'MS 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; diff --git a/src/ami.css/noscript.css b/src/ami.css/noscript.css new file mode 100644 index 0000000..9fb0dd9 --- /dev/null +++ b/src/ami.css/noscript.css @@ -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; +} diff --git a/src/ami.css/sprite.css b/src/ami.css/sprite.css new file mode 100644 index 0000000..5246dad --- /dev/null +++ b/src/ami.css/sprite.css @@ -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; } diff --git a/src/ami.css/themes/beige.css b/src/ami.css/themes/beige.css new file mode 100644 index 0000000..69a9f20 --- /dev/null +++ b/src/ami.css/themes/beige.css @@ -0,0 +1,107 @@ +/* Beige by flashwave */ + +.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; +} diff --git a/src/ami.css/themes/black.css b/src/ami.css/themes/black.css new file mode 100644 index 0000000..5a4a943 --- /dev/null +++ b/src/ami.css/themes/black.css @@ -0,0 +1,103 @@ +/* Black by reemo and flashwave */ + +.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; +} diff --git a/src/ami.css/themes/blue.css b/src/ami.css/themes/blue.css new file mode 100644 index 0000000..ee5f53c --- /dev/null +++ b/src/ami.css/themes/blue.css @@ -0,0 +1,104 @@ +/* Blue by nookls */ + +.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; +} diff --git a/src/ami.css/themes/cobalt.css b/src/ami.css/themes/cobalt.css new file mode 100644 index 0000000..92701c3 --- /dev/null +++ b/src/ami.css/themes/cobalt.css @@ -0,0 +1,104 @@ +/* Cobalt by flashwave , 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; +} diff --git a/src/ami.css/themes/halext.css b/src/ami.css/themes/halext.css new file mode 100644 index 0000000..bf502d4 --- /dev/null +++ b/src/ami.css/themes/halext.css @@ -0,0 +1,108 @@ +/* Halext by freakyfurball and flashwave */ + +.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; +} diff --git a/src/ami.css/themes/legacy.css b/src/ami.css/themes/legacy.css new file mode 100644 index 0000000..a019043 --- /dev/null +++ b/src/ami.css/themes/legacy.css @@ -0,0 +1,107 @@ +/* Legacy by flashwave and reemo */ + +.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; +} diff --git a/src/ami.css/themes/lithium.css b/src/ami.css/themes/lithium.css new file mode 100644 index 0000000..cd99e25 --- /dev/null +++ b/src/ami.css/themes/lithium.css @@ -0,0 +1,107 @@ +/* Lithium by flashwave , 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; +} diff --git a/src/ami.css/themes/mercury.css b/src/ami.css/themes/mercury.css new file mode 100644 index 0000000..144ed2b --- /dev/null +++ b/src/ami.css/themes/mercury.css @@ -0,0 +1,104 @@ +/* Mercury by flashwave , 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; +} diff --git a/src/ami.css/themes/mio.css b/src/ami.css/themes/mio.css new file mode 100644 index 0000000..ac7f222 --- /dev/null +++ b/src/ami.css/themes/mio.css @@ -0,0 +1,124 @@ +/* Mio by flashwave */ + +.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; +} diff --git a/src/ami.css/themes/misuzu.css b/src/ami.css/themes/misuzu.css new file mode 100644 index 0000000..e7bf7fe --- /dev/null +++ b/src/ami.css/themes/misuzu.css @@ -0,0 +1,159 @@ +/* Misuzu by flashwave */ + +.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; +} diff --git a/src/ami.css/themes/nico.css b/src/ami.css/themes/nico.css new file mode 100644 index 0000000..9e07302 --- /dev/null +++ b/src/ami.css/themes/nico.css @@ -0,0 +1,112 @@ +/* NicoFlashii by flashwave and reemo */ + +.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; +} diff --git a/src/ami.css/themes/oxygen.css b/src/ami.css/themes/oxygen.css new file mode 100644 index 0000000..9af93dd --- /dev/null +++ b/src/ami.css/themes/oxygen.css @@ -0,0 +1,105 @@ +/* Oxygen by flashwave , 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; +} diff --git a/src/ami.css/themes/sulfur.css b/src/ami.css/themes/sulfur.css new file mode 100644 index 0000000..6df0edd --- /dev/null +++ b/src/ami.css/themes/sulfur.css @@ -0,0 +1,99 @@ +/* Sulfur by flashwave , 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; +} diff --git a/src/ami.css/themes/techno.css b/src/ami.css/themes/techno.css new file mode 100644 index 0000000..2349dff --- /dev/null +++ b/src/ami.css/themes/techno.css @@ -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; +} diff --git a/src/ami.css/themes/white.css b/src/ami.css/themes/white.css new file mode 100644 index 0000000..5572748 --- /dev/null +++ b/src/ami.css/themes/white.css @@ -0,0 +1,104 @@ +/* White by reemo and flashwave */ + +.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; +} diff --git a/src/ami.css/themes/yuuno.css b/src/ami.css/themes/yuuno.css new file mode 100644 index 0000000..6f084b0 --- /dev/null +++ b/src/ami.css/themes/yuuno.css @@ -0,0 +1,137 @@ +/* Yuuno by flashwave */ + +.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; +} diff --git a/src/ami.html b/src/ami.html new file mode 100644 index 0000000..70fe91a --- /dev/null +++ b/src/ami.html @@ -0,0 +1,30 @@ + + + + + {title} + + + + + + + + + + + diff --git a/src/ami.js/buttons.js b/src/ami.js/buttons.js new file mode 100644 index 0000000..252fd30 --- /dev/null +++ b/src/ami.js/buttons.js @@ -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, + }; +}; diff --git a/src/ami.js/chat.js b/src/ami.js/chat.js new file mode 100644 index 0000000..f012363 --- /dev/null +++ b/src/ami.js/chat.js @@ -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; +}; diff --git a/src/ami.js/colourpick.js b/src/ami.js/colourpick.js new file mode 100644 index 0000000..4d58a1d --- /dev/null +++ b/src/ami.js/colourpick.js @@ -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(); + }, + }; +}; diff --git a/src/ami.js/common.js b/src/ami.js/common.js new file mode 100644 index 0000000..d8468b8 --- /dev/null +++ b/src/ami.js/common.js @@ -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(); +}; diff --git a/src/ami.js/compat.js b/src/ami.js/compat.js new file mode 100644 index 0000000..266b9f3 --- /dev/null +++ b/src/ami.js/compat.js @@ -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; + }; diff --git a/src/ami.js/cookies.js b/src/ami.js/cookies.js new file mode 100644 index 0000000..442bb7d --- /dev/null +++ b/src/ami.js/cookies.js @@ -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, + }; +})(); diff --git a/src/ami.js/copyright.js b/src/ami.js/copyright.js new file mode 100644 index 0000000..1b19b7c --- /dev/null +++ b/src/ami.js/copyright.js @@ -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, + })) + }, + }; +}; diff --git a/src/ami.js/ctx.js b/src/ami.js/ctx.js new file mode 100644 index 0000000..be5cd13 --- /dev/null +++ b/src/ami.js/ctx.js @@ -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') + '

' + 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 += '
' + AmiStrings.getMenuString('eot'); + else + overlay += '
' + info.baka.until.toLocaleString(); + } + overlay += '

' + AmiStrings.getMenuString('back') + ''; + + 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 += '
' + info.baka.until.toLocaleString(); + overlay += '

' + AmiStrings.getMenuString('back') + ''; + + 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; +}; diff --git a/src/ami.js/eeprom.js b/src/ami.js/eeprom.js new file mode 100644 index 0000000..6c4e8cb --- /dev/null +++ b/src/ami.js/eeprom.js @@ -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); + }; +})(); diff --git a/src/ami.js/emotes.js b/src/ami.js/emotes.js new file mode 100644 index 0000000..33ae6fe --- /dev/null +++ b/src/ami.js/emotes.js @@ -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; + }, + }; +}; diff --git a/src/ami.js/inputbox.js b/src/ami.js/inputbox.js new file mode 100644 index 0000000..985d748 --- /dev/null +++ b/src/ami.js/inputbox.js @@ -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; +}; diff --git a/src/ami.js/loadoverlay.js b/src/ami.js/loadoverlay.js new file mode 100644 index 0000000..ef3ff62 --- /dev/null +++ b/src/ami.js/loadoverlay.js @@ -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, + }; +}; diff --git a/src/ami.js/main.js b/src/ami.js/main.js new file mode 100644 index 0000000..01ae8fb --- /dev/null +++ b/src/ami.js/main.js @@ -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(); + }); + }); +})(); diff --git a/src/ami.js/mami/domaintrans.jsx b/src/ami.js/mami/domaintrans.jsx new file mode 100644 index 0000000..6b3426c --- /dev/null +++ b/src/ami.js/mami/domaintrans.jsx @@ -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 =
+
+
+
+ Compatibility Chat +
+
+
sockchat.flashii.net/legacy
+ {arrowsTarget1 =
} +
sockchat.flashii.net
+
+
+
+
+ Flashii Chat +
+
+
sockchat.flashii.net
+ {arrowsTarget2 =
} +
chat.flashii.net
+
+
+
+

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!

+ {exportTidbit =

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.

} +

This screen won't show up again after you press "Continue to chat".

+
+
+ {modernButton =
+ } + {exportButton =
} +
+
+
+
; + + for(let i = 0; i < 5; ++i) + arrowsTarget1.appendChild(â–¼); + for(let i = 0; i < 5; ++i) + arrowsTarget2.appendChild(â–¼); + + if(typeof onExport !== 'function') { + $r(exportTidbit); + $r(modernButton); + $r(exportButton); + } + + return { + appendTo: parent => parent.appendChild(html), + remove: () => $r(html), + }; +}; diff --git a/src/ami.js/mami/settings.js b/src/ami.js/mami/settings.js new file mode 100644 index 0000000..1f485bf --- /dev/null +++ b/src/ami.js/mami/settings.js @@ -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, + }; +})(); diff --git a/src/ami.js/messages.js b/src/ami.js/messages.js new file mode 100644 index 0000000..d334051 --- /dev/null +++ b/src/ami.js/messages.js @@ -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); + }, + }; +}; diff --git a/src/ami.js/mszauth.js b/src/ami.js/mszauth.js new file mode 100644 index 0000000..ebb2e80 --- /dev/null +++ b/src/ami.js/mszauth.js @@ -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(); + }, + }; +}; diff --git a/src/ami.js/notify.js b/src/ami.js/notify.js new file mode 100644 index 0000000..7cb6c8a --- /dev/null +++ b/src/ami.js/notify.js @@ -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); + }); + }, + }; +}; diff --git a/src/ami.js/opticons.js b/src/ami.js/opticons.js new file mode 100644 index 0000000..c572ed1 --- /dev/null +++ b/src/ami.js/opticons.js @@ -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); + }, + }; +}; diff --git a/src/ami.js/reconnect.js b/src/ami.js/reconnect.js new file mode 100644 index 0000000..d20ce16 --- /dev/null +++ b/src/ami.js/reconnect.js @@ -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, + }; +}; diff --git a/src/ami.js/servers.js b/src/ami.js/servers.js new file mode 100644 index 0000000..6d8c39d --- /dev/null +++ b/src/ami.js/servers.js @@ -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); + }, + }; +}; diff --git a/src/ami.js/settings.js b/src/ami.js/settings.js new file mode 100644 index 0000000..d933256 --- /dev/null +++ b/src/ami.js/settings.js @@ -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); + }, + }; +}; diff --git a/src/ami.js/sidebars.js b/src/ami.js/sidebars.js new file mode 100644 index 0000000..db43335 --- /dev/null +++ b/src/ami.js/sidebars.js @@ -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); + }, + }; +}; diff --git a/src/ami.js/sidebars/channelssb.js b/src/ami.js/sidebars/channelssb.js new file mode 100644 index 0000000..a68da75 --- /dev/null +++ b/src/ami.js/sidebars/channelssb.js @@ -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) { + // + }, + }; +}; diff --git a/src/ami.js/sidebars/helpsb.js b/src/ami.js/sidebars/helpsb.js new file mode 100644 index 0000000..f011449 --- /dev/null +++ b/src/ami.js/sidebars/helpsb.js @@ -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, + }; +}; diff --git a/src/ami.js/sidebars/settingssb.js b/src/ami.js/sidebars/settingssb.js new file mode 100644 index 0000000..4602548 --- /dev/null +++ b/src/ami.js/sidebars/settingssb.js @@ -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')); + }, + }; +}; diff --git a/src/ami.js/sidebars/uploadssb.js b/src/ami.js/sidebars/uploadssb.js new file mode 100644 index 0000000..ee0d0e5 --- /dev/null +++ b/src/ami.js/sidebars/uploadssb.js @@ -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')); + }, + }; +}; diff --git a/src/ami.js/sidebars/userssb.js b/src/ami.js/sidebars/userssb.js new file mode 100644 index 0000000..f725ec9 --- /dev/null +++ b/src/ami.js/sidebars/userssb.js @@ -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')); + }, + }; +}; diff --git a/src/ami.js/sockchat.js b/src/ami.js/sockchat.js new file mode 100644 index 0000000..4a2220a --- /dev/null +++ b/src/ami.js/sockchat.js @@ -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; +}; diff --git a/src/ami.js/sound.js b/src/ami.js/sound.js new file mode 100644 index 0000000..cde18fc --- /dev/null +++ b/src/ami.js/sound.js @@ -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; +}; diff --git a/src/ami.js/status.js b/src/ami.js/status.js new file mode 100644 index 0000000..126f132 --- /dev/null +++ b/src/ami.js/status.js @@ -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); + }, + }; +}; diff --git a/src/ami.js/strings.js b/src/ami.js/strings.js new file mode 100644 index 0000000..e008d8d --- /dev/null +++ b/src/ami.js/strings.js @@ -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: ('' + string + ''), + }; + }, + forEachHelpString: function(body) { + for(var i = 0; i < helpText.length; ++i) + body(helpText[i]); + }, + }; +})(); diff --git a/src/ami.js/styles.js b/src/ami.js/styles.js new file mode 100644 index 0000000..3ff9fee --- /dev/null +++ b/src/ami.js/styles.js @@ -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); + }, + }; +}; diff --git a/src/ami.js/submitbox.js b/src/ami.js/submitbox.js new file mode 100644 index 0000000..dbbc732 --- /dev/null +++ b/src/ami.js/submitbox.js @@ -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; +}; diff --git a/src/ami.js/title.js b/src/ami.js/title.js new file mode 100644 index 0000000..2e2ce0b --- /dev/null +++ b/src/ami.js/title.js @@ -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, + }; +}; diff --git a/src/ami.js/ts_10_user.js b/src/ami.js/ts_10_user.js new file mode 100644 index 0000000..c927d0e --- /dev/null +++ b/src/ami.js/ts_10_user.js @@ -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; +})(); diff --git a/src/ami.js/ts_20_ui.js b/src/ami.js/ts_20_ui.js new file mode 100644 index 0000000..9e39ff1 --- /dev/null +++ b/src/ami.js/ts_20_ui.js @@ -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] = "" + text + ""; + } + + 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"), ''); + }); + + 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 = "" + u.username + ""; + 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; +})(); diff --git a/src/ami.js/ts_chat.js b/src/ami.js/ts_chat.js new file mode 100644 index 0000000..5c43d05 --- /dev/null +++ b/src/ami.js/ts_chat.js @@ -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: "{0}", + button: 'b', + buttonStyle: { fontWeight: 'bold' }, + persistSetting: 'bbPersistBold', + persistStyleAttr: 'fontWeight', + persistStyleValue: 'bold', + }, + { + tag: "i", + arg: false, + swap: "{0}", + button: 'i', + buttonStyle: { fontStyle: 'italic' }, + persistSetting: 'bbPersistItalics', + persistStyleAttr: 'fontStyle', + persistStyleValue: 'italic', + }, + { + tag: "u", + arg: false, + swap: "{0}", + button: 'u', + buttonStyle: { textDecoration: 'underline' }, + persistSetting: 'bbPersistUnderline', + persistStyleAttr: 'textDecoration', + persistStyleValue: 'underline', + }, + { + tag: "s", + arg: false, + swap: "{0}", + button: 's', + buttonStyle: { textDecoration: 'line-through' }, + persistSetting: 'bbPersistStrike', + persistStyleAttr: 'textDecoration', + persistStyleValue: 'line-through', + }, + { + tag: "quote", + arg: false, + swap: "{0}", + button: true + }, + { + tag: "code", + arg: false, + swap: "{0}", + button: true + }, + { + tag: "sjis", + arg: false, + swap: "{0}", + button: true, + persistSetting: 'bbPersistSjis', + persistClassName: 'sjis', + }, + { + tag: "color", + arg: true, + rmarg: ";:{}<>&|\\/~'\"", + swap: "{1}", + 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: "{0}" + }, + { + tag: "url", + arg: true, + rmarg: "\"'", + swap: "{1}", + button: true, + bprompt: "urlprompt" + }, + { + tag: "img", + arg: false, + rmin: "\"'", + swap: "{0} [Embed]", + button: true, + enableSetting: 'bbParseImage', + }, + { + tag: "video", + arg: false, + rmin: "\"'", + swap: "{0} [Embed]", + button: true, + enableSetting: 'bbParseVideo', + }, + { + tag: "audio", + arg: false, + rmin: "\"'", + swap: "{0} [Embed]", + button: true, + enableSetting: 'bbParseAudio', + }, + { + tag: "spoiler", + arg: false, + rmin: "\"'", + swap: "*** HIDDEN *** [Reveal]", + 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; +})(); diff --git a/src/ami.js/ts_utils.js b/src/ami.js/ts_utils.js new file mode 100644 index 0000000..7de7468 --- /dev/null +++ b/src/ami.js/ts_utils.js @@ -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, ">", ">"), "<", "<"), "\n", "
"); + }; + 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" ? "userimg" : 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" ? "" : ""+ 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.EmbedVideo = function (link) { + var id = link.parentElement.title; + var holder = link.parentElement.getElementsByTagName("span")[0]; + holder.innerHTML = holder.title == "link" ? "" : ""+ 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.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; +})(); diff --git a/src/ami.js/txtrigs.js b/src/ami.js/txtrigs.js new file mode 100644 index 0000000..bbb6bb7 --- /dev/null +++ b/src/ami.js/txtrigs.js @@ -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, + }; +}; diff --git a/src/ami.js/users.js b/src/ami.js/users.js new file mode 100644 index 0000000..6a10096 --- /dev/null +++ b/src/ami.js/users.js @@ -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 = '<', afkSfx = '>_', 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; + }, + }; +}; diff --git a/src/ami.js/utility.js b/src/ami.js/utility.js new file mode 100644 index 0000000..41e3689 --- /dev/null +++ b/src/ami.js/utility.js @@ -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; + } +}; diff --git a/src/ami.js/watcher.js b/src/ami.js/watcher.js new file mode 100644 index 0000000..e7960ca --- /dev/null +++ b/src/ami.js/watcher.js @@ -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; + }, + }; +}; diff --git a/src/ami.js/z_eepromv1.js b/src/ami.js/z_eepromv1.js new file mode 100644 index 0000000..f9abc87 --- /dev/null +++ b/src/ami.js/z_eepromv1.js @@ -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(); +}; diff --git a/src/assproc.js b/src/assproc.js new file mode 100644 index 0000000..8fb7dec --- /dev/null +++ b/src/assproc.js @@ -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}`); + } +}; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..d825d83 --- /dev/null +++ b/src/utils.js @@ -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); +};