diff --git a/.prettierignore b/.prettierignore deleted file mode 120000 index 3e4e48b..0000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -.gitignore \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 0967ef4..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/build.js b/build.js index 4f895e1..a6d9433 100644 --- a/build.js +++ b/build.js @@ -1,280 +1,245 @@ -const pug = require("pug"); -const sass = require("sass"); -const fs = require("fs").promises; -const os = require("os"); -const crypto = require("crypto"); -const path = require("path"); -const pj = path.join; -const babel = require("@babel/core"); -const fetch = require("node-fetch"); -const chalk = require("chalk"); -const hint = require("jshint").JSHINT; +const pug = require("pug") +const sass = require("sass") +const fs = require("fs").promises +const os = require("os") +const crypto = require("crypto") +const path = require("path") +const pj = path.join +const babel = require("@babel/core") +const fetch = require("node-fetch") +const chalk = require("chalk") +const hint = require("jshint").JSHINT -process.chdir(pj(__dirname, "src")); +process.chdir(pj(__dirname, "src")) -const buildDir = "../build"; +const buildDir = "../build" -const validationQueue = []; -const validationHost = - os.hostname() === "future" - ? "http://localhost:8888/" - : "http://validator.w3.org/nu/"; -const static = new Map(); -const links = new Map(); -const pugLocals = { static, links }; +const validationQueue = [] +const validationHost = os.hostname() === "future" ? "http://localhost:8888/" : "http://validator.w3.org/nu/" +const static = new Map() +const links = new Map() +const pugLocals = {static, links} -const spec = require("./spec.js"); +const spec = require("./spec.js") function hash(buffer) { - return crypto.createHash("sha256").update(buffer).digest("hex").slice(0, 10); + return crypto.createHash("sha256").update(buffer).digest("hex").slice(0, 10) } function validate(filename, body, type) { - const promise = fetch(validationHost + "?out=json", { - method: "POST", - body, - headers: { - "content-type": `text/${type}; charset=UTF-8`, - }, - }) - .then((res) => res.json()) - .then((root) => { - return function cont() { - let concerningMessages = 0; - for (const message of root.messages) { - if (message.hiliteStart) { - let type = message.type; - if (message.type === "error") { - type = chalk.red("error"); - } else if (message.type === "warning") { - type = chalk.yellow("warning"); - } else { - continue; // don't care about info - } - let match; - if ( - (match = message.message.match( - /Property “([\w-]+)” doesn't exist.$/ - )) - ) { - // allow these properties specifically - if ( - [ - "scrollbar-width", - "scrollbar-color", - "overflow-anchor", - ].includes(match[1]) - ) { - continue; - } - } - concerningMessages++; - console.log(`validation: ${type} in ${filename}`); - console.log(` ${message.message}`); - const text = message.extract - .replace(/\n/g, "⏎") - .replace(/\t/g, " "); - console.log( - chalk.grey( - " " + - text.slice(0, message.hiliteStart) + - chalk.inverse( - text.substr(message.hiliteStart, message.hiliteLength) - ) + - text.slice(message.hiliteStart + message.hiliteLength) - ) - ); - } else { - console.log(message); - } - } - if (!concerningMessages) { - console.log(`validation: ${chalk.green("ok")} for ${filename}`); - } - }; - }); - validationQueue.push(promise); - return promise; + const promise = fetch(validationHost+"?out=json", { + method: "POST", + body, + headers: { + "content-type": `text/${type}; charset=UTF-8` + } + }).then(res => res.json()).then(root => { + return function cont() { + let concerningMessages = 0 + for (const message of root.messages) { + if (message.hiliteStart) { + let type = message.type + if (message.type === "error") { + type = chalk.red("error") + } else if (message.type === "warning") { + type = chalk.yellow("warning") + } else { + continue // don't care about info + } + let match + if (match = message.message.match(/Property “([\w-]+)” doesn't exist.$/)) { + // allow these properties specifically + if (["scrollbar-width", "scrollbar-color", "overflow-anchor"].includes(match[1])) { + continue + } + } + concerningMessages++ + console.log(`validation: ${type} in ${filename}`) + console.log(` ${message.message}`) + const text = message.extract.replace(/\n/g, "⏎").replace(/\t/g, " ") + console.log(chalk.grey( + " " + + text.slice(0, message.hiliteStart) + + chalk.inverse(text.substr(message.hiliteStart, message.hiliteLength)) + + text.slice(message.hiliteStart+message.hiliteLength) + )) + } else { + console.log(message) + } + } + if (!concerningMessages) { + console.log(`validation: ${chalk.green("ok")} for ${filename}`) + } + } + }) + validationQueue.push(promise) + return promise } function runHint(filename, source) { - hint(source, { - esversion: 9, - undef: true, - // unused: true, - loopfunc: true, - globals: ["console", "URLSearchParams"], - browser: true, - asi: true, - }); - const result = hint.data(); - let problems = 0; - if (result.errors) { - for (const error of result.errors) { - if (error.evidence) { - const text = error.evidence.replace(/\t/g, " "); - if (["W014"].includes(error.code)) continue; - let type = error.code.startsWith("W") - ? chalk.yellow("warning") - : chalk.red("error"); - console.log(`hint: ${type} in ${filename}`); - console.log( - ` ${error.line}:${error.character}: ${error.reason} (${error.code})` - ); - console.log( - chalk.gray( - " " + - text.slice(0, error.character) + - chalk.inverse(text.substr(error.character, 1)) + - text.slice(error.character + 1) - ) - ); - problems++; - } - } - } - if (problems) { - console.log(`hint: ${chalk.cyan(problems + " problems")} in ${filename}`); - } else { - console.log(`hint: ${chalk.green("ok")} for ${filename}`); - } + hint(source, { + esversion: 9, + undef: true, + // unused: true, + loopfunc: true, + globals: ["console", "URLSearchParams"], + browser: true, + asi: true, + }) + const result = hint.data() + let problems = 0 + if (result.errors) { + for (const error of result.errors) { + if (error.evidence) { + const text = error.evidence.replace(/\t/g, " ") + if ([ + "W014" + ].includes(error.code)) continue + let type = error.code.startsWith("W") ? chalk.yellow("warning") : chalk.red("error") + console.log(`hint: ${type} in ${filename}`) + console.log(` ${error.line}:${error.character}: ${error.reason} (${error.code})`) + console.log(chalk.gray( + " " + + text.slice(0, error.character) + + chalk.inverse(text.substr(error.character, 1)) + + text.slice(error.character+1) + )) + problems++ + } + } + } + if (problems) { + console.log(`hint: ${chalk.cyan(problems+" problems")} in ${filename}`) + } else { + console.log(`hint: ${chalk.green("ok")} for ${filename}`) + } } async function addFile(sourcePath, targetPath) { - const contents = await fs.readFile(pj(".", sourcePath), { encoding: null }); - static.set(sourcePath, `${targetPath}?static=${hash(contents)}`); - fs.writeFile(pj(buildDir, targetPath), contents); + const contents = await fs.readFile(pj(".", sourcePath), {encoding: null}) + static.set(sourcePath, `${targetPath}?static=${hash(contents)}`) + fs.writeFile(pj(buildDir, targetPath), contents) } async function addJS(sourcePath, targetPath) { - const source = await fs.readFile(pj(".", sourcePath), { encoding: "utf8" }); - static.set(sourcePath, `${targetPath}?static=${hash(source)}`); - runHint(sourcePath, source); - fs.writeFile(pj(buildDir, targetPath), source); + const source = await fs.readFile(pj(".", sourcePath), {encoding: "utf8"}) + static.set(sourcePath, `${targetPath}?static=${hash(source)}`) + runHint(sourcePath, source); + fs.writeFile(pj(buildDir, targetPath), source) } async function addSass(sourcePath, targetPath) { - const renderedCSS = sass.renderSync({ - file: pj(".", sourcePath), - outputStyle: "expanded", - indentType: "tab", - indentWidth: 1, - functions: { - "static($name)": function (name) { - if (!(name instanceof sass.types.String)) { - throw "$name: expected a string"; - } - const result = static.get(name.getValue()); - if (typeof result === "string") { - return new sass.types.String(result); - } else { - throw new Error( - "static file '" + name.getValue() + "' does not exist" - ); - } - }, - }, - }).css; - static.set(sourcePath, `${targetPath}?static=${hash(renderedCSS)}`); - validate(sourcePath, renderedCSS, "css"); - await fs.writeFile(pj(buildDir, targetPath), renderedCSS); + const renderedCSS = sass.renderSync({ + file: pj(".", sourcePath), + outputStyle: "expanded", + indentType: "tab", + indentWidth: 1, + functions: { + "static($name)": function(name) { + if (!(name instanceof sass.types.String)) { + throw "$name: expected a string" + } + const result = static.get(name.getValue()) + if (typeof result === "string") { + return new sass.types.String(result) + } else { + throw new Error("static file '"+name.getValue()+"' does not exist") + } + } + } + }).css + static.set(sourcePath, `${targetPath}?static=${hash(renderedCSS)}`) + validate(sourcePath, renderedCSS, "css") + await fs.writeFile(pj(buildDir, targetPath), renderedCSS) } async function addPug(sourcePath, targetPath) { - function getRelative(staticTarget) { - const pathLayer = ( - path.dirname(targetPath).replace(/\/$/, "").match(/\//g) || [] - ).length; - const prefix = Array(pathLayer).fill("../").join(""); - const result = prefix + staticTarget.replace(/^\//, ""); - if (result) return result; - else return "./"; - } - function getStatic(target) { - return getRelative(static.get(target)); - } - function getStaticName(target) { - return getRelative(static.get(target)).replace(/\?.*$/, ""); - } - function getLink(target) { - return getRelative(links.get(target)); - } - const renderedHTML = pug.compileFile(pj(".", sourcePath), { pretty: true })({ - getStatic, - getStaticName, - getLink, - ...pugLocals, - }); - let renderedWithoutPHP = renderedHTML.replace(/<\?(?:php|=).*?\?>/gms, ""); - validate(sourcePath, renderedWithoutPHP, "html"); - await fs.writeFile(pj(buildDir, targetPath), renderedHTML); + function getRelative(staticTarget) { + const pathLayer = (path.dirname(targetPath).replace(/\/$/, "").match(/\//g) || []).length + const prefix = Array(pathLayer).fill("../").join("") + const result = prefix + staticTarget.replace(/^\//, "") + if (result) return result + else return "./" + } + function getStatic(target) { + return getRelative(static.get(target)) + } + function getStaticName(target) { + return getRelative(static.get(target)).replace(/\?.*$/, "") + } + function getLink(target) { + return getRelative(links.get(target)) + } + const renderedHTML = pug.compileFile(pj(".", sourcePath), {pretty: true})({getStatic, getStaticName, getLink, ...pugLocals}) + let renderedWithoutPHP = renderedHTML.replace(/<\?(?:php|=).*?\?>/gsm, "") + validate(sourcePath, renderedWithoutPHP, "html") + await fs.writeFile(pj(buildDir, targetPath), renderedHTML) } async function addBabel(sourcePath, targetPath) { - const originalCode = await fs.readFile(pj(".", sourcePath), "utf8"); + const originalCode = await fs.readFile(pj(".", sourcePath), "utf8") - const compiled = babel.transformSync(originalCode, { - sourceMaps: false, - sourceType: "script", - presets: [ - [ - "@babel/env", - { - targets: { - ie: 11, - }, - }, - ], - ], - generatorOpts: { - comments: false, - minified: false, - sourceMaps: false, - }, - }); + const compiled = babel.transformSync(originalCode, { + sourceMaps: false, + sourceType: "script", + presets: [ + [ + "@babel/env", { + targets: { + "ie": 11 + } + } + ] + ], + generatorOpts: { + comments: false, + minified: false, + sourceMaps: false, + } + }) - const filenameWithQuery = `${targetPath}?static=${hash(compiled.code)}`; + const filenameWithQuery = `${targetPath}?static=${hash(compiled.code)}` - static.set(sourcePath, filenameWithQuery); + static.set(sourcePath, filenameWithQuery) - await Promise.all([ - fs.writeFile(pj(buildDir, targetPath), originalCode), - fs.writeFile(pj(buildDir, minFilename), compiled.code), - fs.writeFile(pj(buildDir, mapFilename), JSON.stringify(compiled.map)), - ]); + await Promise.all([ + fs.writeFile(pj(buildDir, targetPath), originalCode), + fs.writeFile(pj(buildDir, minFilename), compiled.code), + fs.writeFile(pj(buildDir, mapFilename), JSON.stringify(compiled.map)) + ]) } -(async () => { - // Stage 1: Register - for (const item of spec) { - if (item.type === "pug") { - links.set(item.source, item.target.replace(/index.html$/, "")); - } - } +;(async () => { + // Stage 1: Register + for (const item of spec) { + if (item.type === "pug") { + links.set(item.source, item.target.replace(/index.html$/, "")) + } + } - // Stage 2: Build - for (const item of spec) { - if (item.type === "file") { - await addFile(item.source, item.target); - } else if (item.type === "js") { - await addJS(item.source, item.target); - } else if (item.type === "sass") { - await addSass(item.source, item.target); - } else if (item.type === "babel") { - await addBabel(item.source, item.target); - } else if (item.type === "pug") { - await addPug(item.source, item.target); - } else { - throw new Error("Unknown item type: " + item.type); - } - } + // Stage 2: Build + for (const item of spec) { + if (item.type === "file") { + await addFile(item.source, item.target) + } else if (item.type === "js") { + await addJS(item.source, item.target) + } else if (item.type === "sass") { + await addSass(item.source, item.target) + } else if (item.type === "babel") { + await addBabel(item.source, item.target) + } else if (item.type === "pug") { + await addPug(item.source, item.target) + } else { + throw new Error("Unknown item type: "+item.type) + } + } - console.log(chalk.green("All files emitted.")); + console.log(chalk.green("All files emitted.")) - await Promise.all(validationQueue).then((v) => { - console.log(`validation: using host ${chalk.cyan(validationHost)}`); - v.forEach((cont) => cont()); - }); + await Promise.all(validationQueue).then(v => { + console.log(`validation: using host ${chalk.cyan(validationHost)}`) + v.forEach(cont => cont()) + }) - console.log(chalk.green("Build complete.") + "\n\n------------\n"); -})(); + console.log(chalk.green("Build complete.") + "\n\n------------\n") +})() diff --git a/jsconfig.json b/jsconfig.json index ed609d3..834a9ea 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,9 +1,9 @@ { - "compilerOptions": { - "target": "esnext", - "module": "esnext", - "checkJs": true, - "moduleResolution": "node", - "allowSyntheticDefaultImports": true - } + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "checkJs": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true + } } diff --git a/package-lock.json b/package-lock.json index 9b5293e..e5806d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "carbon", + "name": "cosc212-assignment-1", "version": "1.0.0", "lockfileVersion": 1, "requires": true, @@ -1095,15 +1095,6 @@ "to-fast-properties": "^2.0.0" } }, - "@prettier/plugin-pug": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@prettier/plugin-pug/-/plugin-pug-1.9.0.tgz", - "integrity": "sha512-doLga3EPMPiUgO98aUWXoq8YuPLIwUWX0YbwqnSg2URQ7hKGjxlyEeVlAmrERVI3mm9zbwpEEZ02jw0ROd+5+g==", - "dev": true, - "requires": { - "pug-lexer": "^5.0.0" - } - }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -1913,12 +1904,6 @@ "mkdirp": "^0.5.5" } }, - "prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", - "dev": true - }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", diff --git a/package.json b/package.json index d8c4acc..781fc2f 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,10 @@ "devDependencies": { "@babel/core": "^7.11.1", "@babel/preset-env": "^7.11.0", - "@prettier/plugin-pug": "^1.9.0", "chalk": "^4.1.0", "http-server": "^0.12.3", "jshint": "^2.12.0", "node-fetch": "^2.6.0", - "prettier": "^2.1.2", "pug": "^3.0.0", "sass": "^1.26.10" } diff --git a/spec.js b/spec.js index 7329ec6..0ebbc17 100644 --- a/spec.js +++ b/spec.js @@ -1,117 +1,117 @@ module.exports = [ - { - type: "file", - source: "/assets/fonts/whitney-500.woff", - target: "/static/whitney-500.woff", - }, - { - type: "file", - source: "/assets/fonts/whitney-400.woff", - target: "/static/whitney-400.woff", - }, - { - type: "js", - source: "/js/basic.js", - target: "/static/basic.js", - }, - { - type: "js", - source: "/js/groups.js", - target: "/static/groups.js", - }, - { - type: "js", - source: "/js/chat-input.js", - target: "/static/chat-input.js", - }, - { - type: "js", - source: "/js/room-picker.js", - target: "/static/room-picker.js", - }, - { - type: "js", - source: "/js/store/store.js", - target: "/static/store/store.js", - }, - { - type: "js", - source: "/js/store/Subscribable.js", - target: "/static/store/Subscribable.js", - }, - { - type: "js", - source: "/js/store/SubscribeValue.js", - target: "/static/store/SubscribeValue.js", - }, - { - type: "js", - source: "/js/store/SubscribeMapList.js", - target: "/static/store/SubscribeMapList.js", - }, - { - type: "js", - source: "/js/store/SubscribeSet.js", - target: "/static/store/SubscribeSet.js", - }, - { - type: "js", - source: "/js/sync/sync.js", - target: "/static/sync/sync.js", - }, - { - type: "js", - source: "/js/lsm.js", - target: "/static/lsm.js", - }, - { - type: "js", - source: "/js/Timeline.js", - target: "/static/Timeline.js", - }, - { - type: "js", - source: "/js/Anchor.js", - target: "/static/Anchor.js", - }, - { - type: "js", - source: "/js/chat.js", - target: "/static/chat.js", - }, - { - type: "file", - source: "/assets/fonts/whitney-500.woff", - target: "/static/whitney-500.woff", - }, - { - type: "file", - source: "/assets/icons/directs.svg", - target: "/static/directs.svg", - }, - { - type: "file", - source: "/assets/icons/channels.svg", - target: "/static/channels.svg", - }, - { - type: "file", - source: "/assets/icons/join-event.svg", - target: "/static/join-event.svg", - }, - { - type: "sass", - source: "/sass/main.sass", - target: "/static/main.css", - }, - { - type: "pug", - source: "/home.pug", - target: "/index.html", - }, - { - type: "pug", - source: "/login.pug", - target: "/login.html", - }, -]; + { + type: "file", + source: "/assets/fonts/whitney-500.woff", + target: "/static/whitney-500.woff" + }, + { + type: "file", + source: "/assets/fonts/whitney-400.woff", + target: "/static/whitney-400.woff" + }, + { + type: "js", + source: "/js/basic.js", + target: "/static/basic.js" + }, + { + type: "js", + source: "/js/groups.js", + target: "/static/groups.js" + }, + { + type: "js", + source: "/js/chat-input.js", + target: "/static/chat-input.js" + }, + { + type: "js", + source: "/js/room-picker.js", + target: "/static/room-picker.js" + }, + { + type: "js", + source: "/js/store/store.js", + target: "/static/store/store.js" + }, + { + type: "js", + source: "/js/store/Subscribable.js", + target: "/static/store/Subscribable.js" + }, + { + type: "js", + source: "/js/store/SubscribeValue.js", + target: "/static/store/SubscribeValue.js" + }, + { + type: "js", + source: "/js/store/SubscribeMapList.js", + target: "/static/store/SubscribeMapList.js" + }, + { + type: "js", + source: "/js/store/SubscribeSet.js", + target: "/static/store/SubscribeSet.js" + }, + { + type: "js", + source: "/js/sync/sync.js", + target: "/static/sync/sync.js" + }, + { + type: "js", + source: "/js/lsm.js", + target: "/static/lsm.js" + }, + { + type: "js", + source: "/js/Timeline.js", + target: "/static/Timeline.js" + }, + { + type: "js", + source: "/js/Anchor.js", + target: "/static/Anchor.js" + }, + { + type: "js", + source: "/js/chat.js", + target: "/static/chat.js" + }, + { + type: "file", + source: "/assets/fonts/whitney-500.woff", + target: "/static/whitney-500.woff" + }, + { + type: "file", + source: "/assets/icons/directs.svg", + target: "/static/directs.svg" + }, + { + type: "file", + source: "/assets/icons/channels.svg", + target: "/static/channels.svg" + }, + { + type: "file", + source: "/assets/icons/join-event.svg", + target: "/static/join-event.svg" + }, + { + type: "sass", + source: "/sass/main.sass", + target: "/static/main.css" + }, + { + type: "pug", + source: "/home.pug", + target: "/index.html" + }, + { + type: "pug", + source: "/login.pug", + target: "/login.html" + } +] diff --git a/src/home.pug b/src/home.pug index a33806c..ea11138 100644 --- a/src/home.pug +++ b/src/home.pug @@ -26,32 +26,29 @@ mixin message-notice(content) mixin message-event(icon, content) .c-message-event .c-message-event__inner - img.c-message-event__icon(src=icon, alt="") + img(src=icon alt="").c-message-event__icon = content doctype html html head meta(charset="utf-8") - link(rel="stylesheet", type="text/css", href=getStatic('/sass/main.sass')) - script(type="module", src=getStatic('/js/groups.js')) - script(type="module", src=getStatic('/js/chat-input.js')) - script(type="module", src=getStatic('/js/room-picker.js')) - script(type="module", src=getStatic('/js/sync/sync.js')) - script(type="module", src=getStatic('/js/chat.js')) + link(rel="stylesheet" type="text/css" href=getStatic("/sass/main.sass")) + script(type="module" src=getStatic("/js/groups.js")) + script(type="module" src=getStatic("/js/chat-input.js")) + script(type="module" src=getStatic("/js/room-picker.js")) + script(type="module" src=getStatic("/js/sync/sync.js")) + script(type="module" src=getStatic("/js/chat.js")) title Carbon body main.main .c-groups - #c-groups-display.c-groups__display - #c-group-marker.c-group-marker - #c-groups-list.c-groups__container - #c-rooms.c-rooms + .c-groups__display#c-groups-display + .c-group-marker#c-group-marker + .c-groups__container#c-groups-list + .c-rooms#c-rooms .c-chat - #c-chat-messages.c-chat__messages - #c-chat.c-chat__inner + .c-chat__messages#c-chat-messages + .c-chat__inner#c-chat .c-chat-input - textarea#c-chat-textarea.c-chat-input__textarea( - placeholder="Send a message...", - autocomplete="off" - ) + textarea(placeholder="Send a message..." autocomplete="off").c-chat-input__textarea#c-chat-textarea \ No newline at end of file diff --git a/src/js/Anchor.js b/src/js/Anchor.js index 5cb73ce..c887510 100644 --- a/src/js/Anchor.js +++ b/src/js/Anchor.js @@ -1,15 +1,15 @@ -import { ElemJS } from "./basic.js"; +import {ElemJS} from "./basic.js" class Anchor extends ElemJS { - constructor() { - super("div"); - this.class("c-anchor"); - } + constructor() { + super("div") + this.class("c-anchor") + } - scroll() { - // console.log("anchor scrolled") - this.element.scrollIntoView({ block: "start" }); - } + scroll() { + // console.log("anchor scrolled") + this.element.scrollIntoView({block: "start"}) + } } -export { Anchor }; +export {Anchor} diff --git a/src/js/Timeline.js b/src/js/Timeline.js index da4505b..7c5a508 100644 --- a/src/js/Timeline.js +++ b/src/js/Timeline.js @@ -1,171 +1,148 @@ -import { ElemJS, ejs } from "./basic.js"; -import { Subscribable } from "./store/Subscribable.js"; -import { Anchor } from "./Anchor.js"; +import {ElemJS, ejs} from "./basic.js" +import {Subscribable} from "./store/Subscribable.js" +import {Anchor} from "./Anchor.js" function eventSearch(list, event, min = 0, max = -1) { - if (list.length === 0) return { success: false, i: 0 }; + if (list.length === 0) return {success: false, i: 0} - if (max === -1) max = list.length - 1; - let mid = Math.floor((max + min) / 2); - // success condition - if (list[mid] && list[mid].data.event_id === event.data.event_id) - return { success: true, i: mid }; - // failed condition - if (min >= max) { - while ( - mid !== -1 && - (!list[mid] || - list[mid].data.origin_server_ts > event.data.origin_server_ts) - ) - mid--; - return { - success: false, - i: mid + 1, - }; - } - // recurse (below) - if (list[mid].data.origin_server_ts > event.data.origin_server_ts) - return eventSearch(list, event, min, mid - 1); - // recurse (above) - else return eventSearch(list, event, mid + 1, max); + if (max === -1) max = list.length - 1 + let mid = Math.floor((max + min) / 2) + // success condition + if (list[mid] && list[mid].data.event_id === event.data.event_id) return {success: true, i: mid} + // failed condition + if (min >= max) { + while (mid !== -1 && (!list[mid] || list[mid].data.origin_server_ts > event.data.origin_server_ts)) mid-- + return { + success: false, + i: mid + 1 + } + } + // recurse (below) + if (list[mid].data.origin_server_ts > event.data.origin_server_ts) return eventSearch(list, event, min, mid-1) + // recurse (above) + else return eventSearch(list, event, mid+1, max) } class Event extends ElemJS { - constructor(data) { - super("div"); - this.class("c-message"); - this.data = null; - this.update(data); - } + constructor(data) { + super("div") + this.class("c-message") + this.data = null + this.update(data) + } - update(data) { - this.data = data; - this.render(); - } + update(data) { + this.data = data + this.render() + } - render() { - this.child(this.data.content.body); - } + render() { + this.child(this.data.content.body) + } } class EventGroup extends ElemJS { - constructor(list) { - super("div"); - this.class("c-message-group"); - this.list = list; - this.data = { - sender: list[0].data.sender, - origin_server_ts: list[0].data.origin_server_ts, - }; - this.child( - ejs("div") - .class("c-message-group__avatar") - .child(ejs("div").class("c-message-group__icon")), - (this.messages = ejs("div") - .class("c-message-group__messages") - .child( - ejs("div") - .class("c-message-group__intro") - .child( - ejs("div").class("c-message-group__name").text(this.data.sender), - ejs("div") - .class("c-message-group__date") - .text(this.data.origin_server_ts) - ), - ...this.list - )) - ); - } + constructor(list) { + super("div") + this.class("c-message-group") + this.list = list + this.data = { + sender: list[0].data.sender, + origin_server_ts: list[0].data.origin_server_ts + } + this.child( + ejs("div").class("c-message-group__avatar").child( + ejs("div").class("c-message-group__icon") + ), + this.messages = ejs("div").class("c-message-group__messages").child( + ejs("div").class("c-message-group__intro").child( + ejs("div").class("c-message-group__name").text(this.data.sender), + ejs("div").class("c-message-group__date").text(this.data.origin_server_ts) + ), + ...this.list + ) + ) + } - addEvent(event) { - const index = eventSearch(this.list, event).i; - this.list.splice(index, 0, event); - this.messages.childAt(index + 1, event); - } + addEvent(event) { + const index = eventSearch(this.list, event).i + this.list.splice(index, 0, event) + this.messages.childAt(index + 1, event) + } } class ReactiveTimeline extends ElemJS { - constructor(list) { - super("div"); - this.class("c-event-groups"); - this.list = list; - this.render(); - } + constructor(list) { + super("div") + this.class("c-event-groups") + this.list = list + this.render() + } - addEvent(event) { - const search = eventSearch(this.list, event); - // console.log(search, this.list.map(l => l.data.sender), event.data) - if (!search.success && search.i >= 1) - this.tryAddGroups(event, [search.i - 1, search.i]); - else this.tryAddGroups(event, [search.i]); - } + addEvent(event) { + const search = eventSearch(this.list, event) + // console.log(search, this.list.map(l => l.data.sender), event.data) + if (!search.success && search.i >= 1) this.tryAddGroups(event, [search.i-1, search.i]) + else this.tryAddGroups(event, [search.i]) + } - tryAddGroups(event, indices) { - const success = indices.some((i) => { - if (!this.list[i]) { - // if (printed++ < 100) console.log("tryadd success, created group") - const group = new EventGroup([event]); - this.list.splice(i, 0, group); - this.childAt(i, group); - return true; - } else if ( - this.list[i] && - this.list[i].data.sender === event.data.sender - ) { - // if (printed++ < 100) console.log("tryadd success, using existing group") - this.list[i].addEvent(event); - return true; - } - }); - if (!success) - console.log( - "tryadd failure", - indices, - this.list.map((l) => l.data.sender), - event.data - ); - } + tryAddGroups(event, indices) { + const success = indices.some(i => { + if (!this.list[i]) { + // if (printed++ < 100) console.log("tryadd success, created group") + const group = new EventGroup([event]) + this.list.splice(i, 0, group) + this.childAt(i, group) + return true + } else if (this.list[i] && this.list[i].data.sender === event.data.sender) { + // if (printed++ < 100) console.log("tryadd success, using existing group") + this.list[i].addEvent(event) + return true + } + }) + if (!success) console.log("tryadd failure", indices, this.list.map(l => l.data.sender), event.data) + } - render() { - this.clearChildren(); - this.list.forEach((group) => this.child(group)); - this.anchor = new Anchor(); - this.child(this.anchor); - } + render() { + this.clearChildren() + this.list.forEach(group => this.child(group)) + this.anchor = new Anchor() + this.child(this.anchor) + } } class Timeline extends Subscribable { - constructor() { - super(); - Object.assign(this.events, { - beforeChange: [], - }); - Object.assign(this.eventDeps, { - beforeChange: [], - }); - this.list = []; - this.map = new Map(); - this.reactiveTimeline = new ReactiveTimeline([]); - this.latest = 0; - } + constructor() { + super() + Object.assign(this.events, { + beforeChange: [] + }) + Object.assign(this.eventDeps, { + beforeChange: [] + }) + this.list = [] + this.map = new Map() + this.reactiveTimeline = new ReactiveTimeline([]) + this.latest = 0 + } - updateEvents(events) { - this.broadcast("beforeChange"); - for (const eventData of events) { - this.latest = Math.max(this.latest, eventData.origin_server_ts); - if (this.map.has(eventData.event_id)) { - this.map.get(eventData.event_id).update(eventData); - } else { - const event = new Event(eventData); - this.reactiveTimeline.addEvent(event); - } - } - } + updateEvents(events) { + this.broadcast("beforeChange") + for (const eventData of events) { + this.latest = Math.max(this.latest, eventData.origin_server_ts) + if (this.map.has(eventData.event_id)) { + this.map.get(eventData.event_id).update(eventData) + } else { + const event = new Event(eventData) + this.reactiveTimeline.addEvent(event) + } + } + } - getTimeline() { - return this.reactiveTimeline; - } - /* + getTimeline() { + return this.reactiveTimeline + } +/* getGroupedEvents() { let currentSender = Symbol("N/A") let groups = [] @@ -185,4 +162,4 @@ class Timeline extends Subscribable { */ } -export { Timeline }; +export {Timeline} diff --git a/src/js/basic.js b/src/js/basic.js index c9ffd9f..1f3e695 100644 --- a/src/js/basic.js +++ b/src/js/basic.js @@ -3,13 +3,13 @@ * @template {HTMLElement} T * @returns {T} */ -const q = (s) => document.querySelector(s); +const q = s => document.querySelector(s); /** * Shortcut for querySelectorAll. * @template {HTMLElement} T * @returns {T[]} */ -const qa = (s) => document.querySelectorAll(s); +const qa = s => document.querySelectorAll(s); /** * An easier, chainable, object-oriented way to create and update elements @@ -18,147 +18,143 @@ const qa = (s) => document.querySelectorAll(s); * Created by Cadence Ember in 2018. */ class ElemJS { - constructor(type) { - if (type instanceof HTMLElement) { - // If passed an existing element, bind to it - this.bind(type); - } else { - // Otherwise, create a new detached element to bind to - this.bind(document.createElement(type)); - } - this.children = []; - } + constructor(type) { + if (type instanceof HTMLElement) { + // If passed an existing element, bind to it + this.bind(type); + } else { + // Otherwise, create a new detached element to bind to + this.bind(document.createElement(type)); + } + this.children = []; + } - /** Bind this construct to an existing element on the page. */ - bind(element) { - this.element = element; - this.element.js = this; - return this; - } + /** Bind this construct to an existing element on the page. */ + bind(element) { + this.element = element; + this.element.js = this; + return this; + } - /** Add a class. */ - class() { - for (let name of arguments) if (name) this.element.classList.add(name); - return this; - } + /** Add a class. */ + class() { + for (let name of arguments) if (name) this.element.classList.add(name); + return this; + } - /** Remove a class. */ - removeClass() { - for (let name of arguments) if (name) this.element.classList.remove(name); - return this; - } + /** Remove a class. */ + removeClass() { + for (let name of arguments) if (name) this.element.classList.remove(name); + return this; + } - /** Set a JS property on the element. */ - direct(name, value) { - if (name) this.element[name] = value; - return this; - } + /** Set a JS property on the element. */ + direct(name, value) { + if (name) this.element[name] = value; + return this; + } - /** Set an attribute on the element. */ - attribute(name, value) { - if (name) this.element.setAttribute(name, value != undefined ? value : ""); - return this; - } + /** Set an attribute on the element. */ + attribute(name, value) { + if (name) this.element.setAttribute(name, value != undefined ? value : ""); + return this; + } - /** Set a style on the element. */ - style(name, value) { - if (name) this.element.style[name] = value; - return this; - } + /** Set a style on the element. */ + style(name, value) { + if (name) this.element.style[name] = value; + return this; + } - /** Set the element's ID. */ - id(name) { - if (name) this.element.id = name; - return this; - } + /** Set the element's ID. */ + id(name) { + if (name) this.element.id = name; + return this; + } - /** Attach a callback function to an event on the element. */ - on(name, callback) { - this.element.addEventListener(name, callback); - return this; - } + /** Attach a callback function to an event on the element. */ + on(name, callback) { + this.element.addEventListener(name, callback); + return this; + } - /** Set the element's text. */ - text(name) { - this.element.innerText = name; - return this; - } + /** Set the element's text. */ + text(name) { + this.element.innerText = name; + return this; + } - /** Create a text node and add it to the element. */ - addText(name) { - const node = document.createTextNode(name); - this.element.appendChild(node); - return this; - } + /** Create a text node and add it to the element. */ + addText(name) { + const node = document.createTextNode(name); + this.element.appendChild(node); + return this; + } - /** Set the element's HTML content. */ - html(name) { - this.element.innerHTML = name; - return this; - } + /** Set the element's HTML content. */ + html(name) { + this.element.innerHTML = name; + return this; + } - /** - * Add children to the element. - * Children can either be an instance of ElemJS, in - * which case the element will be appended as a child, - * or a string, in which case the string will be added as a text node. - * Each child should be a parameter to this method. - */ - child(...children) { - for (const toAdd of children) { - if (typeof toAdd === "object" && toAdd !== null) { - // Should be an instance of ElemJS, so append as child - toAdd.parent = this; - this.element.appendChild(toAdd.element); - this.children.push(toAdd); - } else if (typeof toAdd === "string") { - // Is a string, so add as text node - this.addText(toAdd); - } - } - return this; - } + /** + * Add children to the element. + * Children can either be an instance of ElemJS, in + * which case the element will be appended as a child, + * or a string, in which case the string will be added as a text node. + * Each child should be a parameter to this method. + */ + child(...children) { + for (const toAdd of children) { + if (typeof toAdd === "object" && toAdd !== null) { + // Should be an instance of ElemJS, so append as child + toAdd.parent = this; + this.element.appendChild(toAdd.element); + this.children.push(toAdd); + } else if (typeof toAdd === "string") { + // Is a string, so add as text node + this.addText(toAdd); + } + } + return this; + } - childAt(index, toAdd) { - if (typeof toAdd === "object" && toAdd !== null) { - toAdd.parent = this; - this.children.splice(index, 0, toAdd); - if (index >= this.element.childNodes.length) { - this.element.appendChild(toAdd.element); - } else { - this.element.childNodes[index].insertAdjacentElement( - "beforebegin", - toAdd.element - ); - } - } - } + childAt(index, toAdd) { + if (typeof toAdd === "object" && toAdd !== null) { + toAdd.parent = this; + this.children.splice(index, 0, toAdd); + if (index >= this.element.childNodes.length) { + this.element.appendChild(toAdd.element) + } else { + this.element.childNodes[index].insertAdjacentElement("beforebegin", toAdd.element) + } + } + } - /** - * Remove all children from the element. - */ - clearChildren() { - this.children.length = 0; - while (this.element.lastChild) - this.element.removeChild(this.element.lastChild); - } + /** + * Remove all children from the element. + */ + clearChildren() { + this.children.length = 0; + while (this.element.lastChild) this.element.removeChild(this.element.lastChild); + } - /** - * Remove this element. - */ - remove() { - let index; - if (this.parent && (index = this.parent.children.indexOf(this)) !== -1) { - this.parent.children.splice(index, 1); - } - this.parent = null; - this.element.remove(); - } + /** + * Remove this element. + */ + remove() { + let index; + if (this.parent && (index = this.parent.children.indexOf(this)) !== -1) { + this.parent.children.splice(index, 1); + } + this.parent = null; + this.element.remove(); + } } /** Shortcut for `new ElemJS`. */ function ejs(tag) { - return new ElemJS(tag); + return new ElemJS(tag); } -export { q, qa, ElemJS, ejs }; +export {q, qa, ElemJS, ejs} diff --git a/src/js/chat-input.js b/src/js/chat-input.js index 6bf3e2f..d8c0a35 100644 --- a/src/js/chat-input.js +++ b/src/js/chat-input.js @@ -1,60 +1,53 @@ -import { q } from "./basic.js"; -import { store } from "./store/store.js"; -import * as lsm from "./lsm.js"; -import { chat } from "./chat.js"; +import {q} from "./basic.js" +import {store} from "./store/store.js" +import * as lsm from "./lsm.js" +import {chat} from "./chat.js" -let sentIndex = 0; +let sentIndex = 0 -const input = q("#c-chat-textarea"); +const input = q("#c-chat-textarea") store.activeRoom.subscribe("changeSelf", () => { - if (store.activeRoom.exists()) { - input.focus(); - } -}); + if (store.activeRoom.exists()) { + input.focus() + } +}) -input.addEventListener("keydown", (event) => { - if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey) { - event.preventDefault(); - const body = input.value; - send(input.value); - input.value = ""; - fixHeight(); - } -}); +input.addEventListener("keydown", event => { + if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey) { + event.preventDefault() + const body = input.value + send(input.value) + input.value = "" + fixHeight() + } +}) input.addEventListener("input", () => { - fixHeight(); -}); + fixHeight() +}) function fixHeight() { - input.style.height = "0px"; - // console.log(input.clientHeight, input.scrollHeight) - input.style.height = input.scrollHeight + 1 + "px"; + input.style.height = "0px" + // console.log(input.clientHeight, input.scrollHeight) + input.style.height = (input.scrollHeight + 1) + "px" } function getTxnId() { - return Date.now() + sentIndex++; + return Date.now() + (sentIndex++) } function send(body) { - if (!store.activeRoom.exists()) return; - const id = store.activeRoom.value().id; - return fetch( - `${lsm.get( - "domain" - )}/_matrix/client/r0/rooms/${id}/send/m.room.message/${getTxnId()}?access_token=${lsm.get( - "access_token" - )}`, - { - method: "PUT", - body: JSON.stringify({ - msgtype: "m.text", - body, - }), - headers: { - "Content-Type": "application/json", - }, - } - ); + if (!store.activeRoom.exists()) return + const id = store.activeRoom.value().id + return fetch(`${lsm.get("domain")}/_matrix/client/r0/rooms/${id}/send/m.room.message/${getTxnId()}?access_token=${lsm.get("access_token")}`, { + method: "PUT", + body: JSON.stringify({ + msgtype: "m.text", + body + }), + headers: { + "Content-Type": "application/json" + } + }) } diff --git a/src/js/chat.js b/src/js/chat.js index f0199f6..9958dc3 100644 --- a/src/js/chat.js +++ b/src/js/chat.js @@ -1,72 +1,65 @@ -import { ElemJS, q, ejs } from "./basic.js"; -import { store } from "./store/store.js"; +import {ElemJS, q, ejs} from "./basic.js" +import {store} from "./store/store.js" -const chatMessages = q("#c-chat-messages"); +const chatMessages = q("#c-chat-messages") class Chat extends ElemJS { - constructor() { - super(q("#c-chat")); + constructor() { + super(q("#c-chat")) - this.removableSubscriptions = []; + this.removableSubscriptions = [] - store.activeRoom.subscribe("changeSelf", this.changeRoom.bind(this)); + store.activeRoom.subscribe("changeSelf", this.changeRoom.bind(this)) - this.render(); - } + this.render() + } - unsubscribe() { - this.removableSubscriptions.forEach(({ name, target, subscription }) => { - target.unsubscribe(name, subscription); - }); - this.removableSubscriptions.length = 0; - } + unsubscribe() { + this.removableSubscriptions.forEach(({name, target, subscription}) => { + target.unsubscribe(name, subscription) + }) + this.removableSubscriptions.length = 0 + } - changeRoom() { - // disconnect from the previous room - this.unsubscribe(); - // connect to the new room's timeline updater - if (store.activeRoom.exists()) { - const timeline = store.activeRoom.value().timeline; - const subscription = () => { - // scroll anchor does not work if the timeline is scrolled to the top. - // at the start, when there are not enough messages for a full screen, this is the case. - // once there are enough messages that scrolling is necessary, we initiate a scroll down to activate the scroll anchor. - let oldDifference = - chatMessages.scrollHeight - chatMessages.clientHeight; - setTimeout(() => { - let newDifference = - chatMessages.scrollHeight - chatMessages.clientHeight; - // console.log("height difference", oldDifference, newDifference) - if (oldDifference < 24) { - // this is jank - this.element.parentElement.scrollBy(0, 1000); - } - }, 0); - }; - const name = "beforeChange"; - this.removableSubscriptions.push({ - name, - target: timeline, - subscription, - }); - timeline.subscribe(name, subscription); - } - this.render(); - } + changeRoom() { + // disconnect from the previous room + this.unsubscribe() + // connect to the new room's timeline updater + if (store.activeRoom.exists()) { + const timeline = store.activeRoom.value().timeline + const subscription = () => { + // scroll anchor does not work if the timeline is scrolled to the top. + // at the start, when there are not enough messages for a full screen, this is the case. + // once there are enough messages that scrolling is necessary, we initiate a scroll down to activate the scroll anchor. + let oldDifference = chatMessages.scrollHeight - chatMessages.clientHeight + setTimeout(() => { + let newDifference = chatMessages.scrollHeight - chatMessages.clientHeight + // console.log("height difference", oldDifference, newDifference) + if (oldDifference < 24) { // this is jank + this.element.parentElement.scrollBy(0, 1000) + } + }, 0) + } + const name = "beforeChange" + this.removableSubscriptions.push({name, target: timeline, subscription}) + timeline.subscribe(name, subscription) + } + this.render() + } - render() { - this.clearChildren(); - if (store.activeRoom.exists()) { - const reactiveTimeline = store.activeRoom.value().timeline.getTimeline(); - this.child(reactiveTimeline); - setTimeout(() => { - this.element.parentElement.scrollBy(0, 1); - reactiveTimeline.anchor.scroll(); - }, 0); - } - } + render() { + this.clearChildren() + if (store.activeRoom.exists()) { + const reactiveTimeline = store.activeRoom.value().timeline.getTimeline() + this.child(reactiveTimeline) + setTimeout(() => { + this.element.parentElement.scrollBy(0, 1) + reactiveTimeline.anchor.scroll() + }, 0) + } + } } -const chat = new Chat(); +const chat = new Chat() -export { chat }; +export {chat} diff --git a/src/js/groups.js b/src/js/groups.js index 7e12fd1..0f209a9 100644 --- a/src/js/groups.js +++ b/src/js/groups.js @@ -1,14 +1,14 @@ -import { q } from "./basic.js"; +import {q} from "./basic.js" -let state = "CLOSED"; +let state = "CLOSED" -const groups = q("#c-groups-display"); -const rooms = q("#c-rooms"); +const groups = q("#c-groups-display") +const rooms = q("#c-rooms") groups.addEventListener("click", () => { - groups.classList.add("c-groups__display--closed"); -}); + groups.classList.add("c-groups__display--closed") +}) rooms.addEventListener("mouseout", () => { - groups.classList.remove("c-groups__display--closed"); -}); + groups.classList.remove("c-groups__display--closed") +}) diff --git a/src/js/lsm.js b/src/js/lsm.js index 8c14b13..7338343 100644 --- a/src/js/lsm.js +++ b/src/js/lsm.js @@ -1,11 +1,11 @@ function get(name) { - return localStorage.getItem(name); + return localStorage.getItem(name) } function set(name, value) { - return localStorage.setItem(name, value); + return localStorage.setItem(name, value) } -window.lsm = { get, set }; +window.lsm = {get, set} -export { get, set }; +export {get, set} diff --git a/src/js/room-picker.js b/src/js/room-picker.js index 8125529..9fc99f5 100644 --- a/src/js/room-picker.js +++ b/src/js/room-picker.js @@ -1,247 +1,235 @@ -import { q, ElemJS, ejs } from "./basic.js"; -import { store } from "./store/store.js"; -import { Timeline } from "./Timeline.js"; -import * as lsm from "./lsm.js"; +import {q, ElemJS, ejs} from "./basic.js" +import {store} from "./store/store.js" +import {Timeline} from "./Timeline.js" +import * as lsm from "./lsm.js" function resolveMxc(url, size, method) { - const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1); - if (size && method) { - return `${lsm.get( - "domain" - )}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`; - } else { - return `${lsm.get("domain")}/_matrix/media/r0/download/${server}/${id}`; - } + const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1) + if (size && method) { + return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}` + } else { + return `${lsm.get("domain")}/_matrix/media/r0/download/${server}/${id}` + } } class ActiveGroupMarker extends ElemJS { - constructor() { - super(q("#c-group-marker")); - store.activeGroup.subscribe("changeSelf", this.render.bind(this)); - } + constructor() { + super(q("#c-group-marker")) + store.activeGroup.subscribe("changeSelf", this.render.bind(this)) + } - render() { - if (store.activeGroup.exists()) { - const group = store.activeGroup.value(); - this.style("opacity", 1); - this.style("transform", `translateY(${group.element.offsetTop}px)`); - } else { - this.style("opacity", 0); - } - } + render() { + if (store.activeGroup.exists()) { + const group = store.activeGroup.value() + this.style("opacity", 1) + this.style("transform", `translateY(${group.element.offsetTop}px)`) + } else { + this.style("opacity", 0) + } + } } -const activeGroupMarker = new ActiveGroupMarker(); +const activeGroupMarker = new ActiveGroupMarker() class Group extends ElemJS { - constructor(key, data) { - super("div"); + constructor(key, data) { + super("div") - this.data = data; - this.order = this.data.order; + this.data = data + this.order = this.data.order - this.class("c-group"); - this.child( - this.data.icon - ? ejs("img").class("c-group__icon").attribute("src", this.data.icon) - : ejs("div").class("c-group__icon"), - ejs("div").class("c-group__name").text(this.data.name) - ); + this.class("c-group") + this.child( + (this.data.icon + ? ejs("img").class("c-group__icon").attribute("src", this.data.icon) + : ejs("div").class("c-group__icon") + ), + ejs("div").class("c-group__name").text(this.data.name) + ) - this.on("click", this.onClick.bind(this)); + this.on("click", this.onClick.bind(this)) - store.activeGroup.subscribe("changeSelf", this.render.bind(this)); - } + store.activeGroup.subscribe("changeSelf", this.render.bind(this)) + } - render() { - const active = store.activeGroup.value() === this; - this.element.classList[active ? "add" : "remove"]("c-group--active"); - } + render() { + const active = store.activeGroup.value() === this + this.element.classList[active ? "add" : "remove"]("c-group--active") + } - onClick() { - store.activeGroup.set(this); - } + onClick() { + store.activeGroup.set(this) + } } class Room extends ElemJS { - constructor(id, data) { - super("div"); + constructor(id, data) { + super("div") - this.id = id; - this.data = data; - this.timeline = new Timeline(); - this.group = null; + this.id = id + this.data = data + this.timeline = new Timeline() + this.group = null - this.class("c-room"); + this.class("c-room") - this.on("click", this.onClick.bind(this)); - store.activeRoom.subscribe("changeSelf", this.render.bind(this)); + this.on("click", this.onClick.bind(this)) + store.activeRoom.subscribe("changeSelf", this.render.bind(this)) - this.render(); - } + this.render() + } - get order() { - if (this.group) { - let chars = 36; - let total = 0; - const name = this.getName(); - for (let i = 0; i < name.length; i++) { - const c = name[i]; - let d = 0; - if (c >= "A" && c <= "Z") d = c.charCodeAt(0) - 65 + 10; - else if (c >= "a" && c <= "z") d = c.charCodeAt(0) - 97 + 10; - else if (c >= "0" && c <= "9") d = +c; - total += d * chars ** -i; - } - return total; - } else { - return -this.timeline.latest; - } - } + get order() { + if (this.group) { + let chars = 36 + let total = 0 + const name = this.getName() + for (let i = 0; i < name.length; i++) { + const c = name[i] + let d = 0 + if (c >= "A" && c <= "Z") d = c.charCodeAt(0) - 65 + 10 + else if (c >= "a" && c <= "z") d = c.charCodeAt(0) - 97 + 10 + else if (c >= "0" && c <= "9") d = +c + total += d * chars ** (-i) + } + return total + } else { + return -this.timeline.latest + } + } - getName() { - let name = this.data.state.events.find((e) => e.type === "m.room.name"); - if (name) { - name = name.content.name; - } else { - const users = this.data.summary["m.heroes"]; - const usernames = users.map((u) => (u.match(/^@([^:]+):/) || [])[1] || u); - name = usernames.join(", "); - } - return name; - } + getName() { + let name = this.data.state.events.find(e => e.type === "m.room.name") + if (name) { + name = name.content.name + } else { + const users = this.data.summary["m.heroes"] + const usernames = users.map(u => (u.match(/^@([^:]+):/) || [])[1] || u) + name = usernames.join(", ") + } + return name + } - getIcon() { - const avatar = this.data.state.events.find( - (e) => e.type === "m.room.avatar" - ); - if (avatar) { - return resolveMxc( - avatar.content.url || avatar.content.avatar_url, - 32, - "crop" - ); - } else { - return null; - } - } + getIcon() { + const avatar = this.data.state.events.find(e => e.type === "m.room.avatar") + if (avatar) { + return resolveMxc(avatar.content.url || avatar.content.avatar_url, 32, "crop") + } else { + return null + } + } - isDirect() { - return store.directs.has(this.id); - } + isDirect() { + return store.directs.has(this.id) + } - setGroup(id) { - this.group = id; - } + setGroup(id) { + this.group = id + } - getGroup() { - if (this.group) { - return store.groups.get(this.group).value(); - } else { - return this.isDirect() - ? store.groups.get("directs").value() - : store.groups.get("channels").value(); - } - } + getGroup() { + if (this.group) { + return store.groups.get(this.group).value() + } else { + return this.isDirect() ? store.groups.get("directs").value() : store.groups.get("channels").value() + } + } - onClick() { - store.activeRoom.set(this); - } + onClick() { + store.activeRoom.set(this) + } - render() { - this.clearChildren(); - // data - const icon = this.getIcon(); - if (icon) { - this.child(ejs("img").class("c-room__icon").attribute("src", icon)); - } else { - this.child(ejs("div").class("c-room__icon", "c-room__icon--no-icon")); - } - this.child(ejs("div").class("c-room__name").text(this.getName())); - // active - const active = store.activeRoom.value() === this; - this.element.classList[active ? "add" : "remove"]("c-room--active"); - } + render() { + this.clearChildren() + // data + const icon = this.getIcon() + if (icon) { + this.child(ejs("img").class("c-room__icon").attribute("src", icon)) + } else { + this.child(ejs("div").class("c-room__icon", "c-room__icon--no-icon")) + } + this.child(ejs("div").class("c-room__name").text(this.getName())) + // active + const active = store.activeRoom.value() === this + this.element.classList[active ? "add" : "remove"]("c-room--active") + } } class Rooms extends ElemJS { - constructor() { - super(q("#c-rooms")); + constructor() { + super(q("#c-rooms")) - this.roomData = []; - this.rooms = []; + this.roomData = [] + this.rooms = [] - store.rooms.subscribe("askAdd", this.askAdd.bind(this)); - store.rooms.subscribe("addItem", this.addItem.bind(this)); - // store.rooms.subscribe("changeItem", this.render.bind(this)) - store.activeGroup.subscribe("changeSelf", this.render.bind(this)); - store.directs.subscribe("changeItem", this.render.bind(this)); - store.newEvents.subscribe("changeSelf", this.sort.bind(this)); + store.rooms.subscribe("askAdd", this.askAdd.bind(this)) + store.rooms.subscribe("addItem", this.addItem.bind(this)) + // store.rooms.subscribe("changeItem", this.render.bind(this)) + store.activeGroup.subscribe("changeSelf", this.render.bind(this)) + store.directs.subscribe("changeItem", this.render.bind(this)) + store.newEvents.subscribe("changeSelf", this.sort.bind(this)) - this.render(); - } + this.render() + } - sort() { - store.rooms.sort(); - this.render(); - } + sort() { + store.rooms.sort() + this.render() + } - askAdd(event, { key, data }) { - const room = new Room(key, data); - store.rooms.addEnd(key, room); - } + askAdd(event, {key, data}) { + const room = new Room(key, data) + store.rooms.addEnd(key, room) + } - addItem(event, key) { - const room = store.rooms.get(key).value(); - if (room.getGroup() === store.activeGroup.value()) { - this.child(room); - } - } + addItem(event, key) { + const room = store.rooms.get(key).value() + if (room.getGroup() === store.activeGroup.value()) { + this.child(room) + } + } - render() { - this.clearChildren(); - let first = null; - // set room list - store.rooms.forEach((id, room) => { - if (room.value().getGroup() === store.activeGroup.value()) { - if (!first) first = room.value(); - this.child(room.value()); - } - }); - // if needed, change the active room to be an item in the room list - if ( - !store.activeRoom.exists() || - store.activeRoom.value().getGroup() !== store.activeGroup.value() - ) { - if (first) { - store.activeRoom.set(first); - } else { - store.activeRoom.delete(); - } - } - } + render() { + this.clearChildren() + let first = null + // set room list + store.rooms.forEach((id, room) => { + if (room.value().getGroup() === store.activeGroup.value()) { + if (!first) first = room.value() + this.child(room.value()) + } + }) + // if needed, change the active room to be an item in the room list + if (!store.activeRoom.exists() || store.activeRoom.value().getGroup() !== store.activeGroup.value()) { + if (first) { + store.activeRoom.set(first) + } else { + store.activeRoom.delete() + } + } + } } -const rooms = new Rooms(); +const rooms = new Rooms() class Groups extends ElemJS { - constructor() { - super(q("#c-groups-list")); + constructor() { + super(q("#c-groups-list")) - store.groups.subscribe("askAdd", this.askAdd.bind(this)); - store.groups.subscribe("changeItem", this.render.bind(this)); - } + store.groups.subscribe("askAdd", this.askAdd.bind(this)) + store.groups.subscribe("changeItem", this.render.bind(this)) + } - askAdd(event, { key, data }) { - const group = new Group(key, data); - store.groups.addEnd(key, group); - store.groups.sort(); - } + askAdd(event, {key, data}) { + const group = new Group(key, data) + store.groups.addEnd(key, group) + store.groups.sort() + } - render() { - this.clearChildren(); - store.groups.forEach((key, item) => { - this.child(item.value()); - }); - } + render() { + this.clearChildren() + store.groups.forEach((key, item) => { + this.child(item.value()) + }) + } } -const groups = new Groups(); +const groups = new Groups() diff --git a/src/js/store/Subscribable.js b/src/js/store/Subscribable.js index 99a987a..6c7640e 100644 --- a/src/js/store/Subscribable.js +++ b/src/js/store/Subscribable.js @@ -1,45 +1,38 @@ class Subscribable { - constructor() { - this.events = { - addSelf: [], - editSelf: [], - removeSelf: [], - changeSelf: [], - }; - this.eventDeps = { - addSelf: ["changeSelf"], - editSelf: ["changeSelf"], - removeSelf: ["changeSelf"], - changeSelf: [], - }; - } + constructor() { + this.events = { + addSelf: [], + editSelf: [], + removeSelf: [], + changeSelf: [] + } + this.eventDeps = { + addSelf: ["changeSelf"], + editSelf: ["changeSelf"], + removeSelf: ["changeSelf"], + changeSelf: [] + } + } - subscribe(event, callback) { - if (this.events[event]) { - this.events[event].push(callback); - } else { - throw new Error( - `Cannot subscribe to non-existent event ${event}, available events are: ${Object.keys( - this.events - ).join(", ")}` - ); - } - } + subscribe(event, callback) { + if (this.events[event]) { + this.events[event].push(callback) + } else { + throw new Error(`Cannot subscribe to non-existent event ${event}, available events are: ${Object.keys(this.events).join(", ")}`) + } + } - unsubscribe(event, callback) { - const index = this.events[event].indexOf(callback); - if (index === -1) - throw new Error( - `Tried to remove a nonexisting subscription from event ${event}` - ); - this.events[event].splice(index, 1); - } + unsubscribe(event, callback) { + const index = this.events[event].indexOf(callback) + if (index === -1) throw new Error(`Tried to remove a nonexisting subscription from event ${event}`) + this.events[event].splice(index, 1) + } - broadcast(event, data) { - this.eventDeps[event].concat(event).forEach((eventName) => { - this.events[eventName].forEach((f) => f(event, data)); - }); - } + broadcast(event, data) { + this.eventDeps[event].concat(event).forEach(eventName => { + this.events[eventName].forEach(f => f(event, data)) + }) + } } -export { Subscribable }; +export {Subscribable} diff --git a/src/js/store/SubscribeMap.js b/src/js/store/SubscribeMap.js index a561948..562d70f 100644 --- a/src/js/store/SubscribeMap.js +++ b/src/js/store/SubscribeMap.js @@ -1,41 +1,41 @@ -import { Subscribable } from "./Subscribable.js"; -import { SubscribeValue } from "./SubscribeValue.js"; +import {Subscribable} from "./Subscribable.js" +import {SubscribeValue} from "./SubscribeValue.js" class SubscribeMap extends Subscribable { - constructor() { - super(); - Object.assign(this.events, { - addItem: [], - changeItem: [], - removeItem: [], - }); - this.map = new Map(); - } + constructor() { + super() + Object.assign(this.events, { + addItem: [], + changeItem: [], + removeItem: [] + }) + this.map = new Map() + } - has(key) { - return this.map.has(key) && this.map.get(key).exists(); - } + has(key) { + return this.map.has(key) && this.map.get(key).exists() + } - get(key) { - if (this.map.has(key)) { - return this.map.get(key); - } else { - this.map.set(key, new SubscribeValue()); - } - } + get(key) { + if (this.map.has(key)) { + return this.map.get(key) + } else { + this.map.set(key, new SubscribeValue()) + } + } - set(key, value) { - let s; - if (this.map.has(key)) { - s = this.map.get(key).set(value); - this.broadcast("changeItem", key); - } else { - s = new SubscribeValue().set(value); - this.map.set(key, s); - this.broadcast("addItem", key); - } - return s; - } + set(key, value) { + let s + if (this.map.has(key)) { + s = this.map.get(key).set(value) + this.broadcast("changeItem", key) + } else { + s = new SubscribeValue().set(value) + this.map.set(key, s) + this.broadcast("addItem", key) + } + return s + } } -export { SubscribeMap }; +export {SubscribeMap} diff --git a/src/js/store/SubscribeMapList.js b/src/js/store/SubscribeMapList.js index 66f8a36..b26d58c 100644 --- a/src/js/store/SubscribeMapList.js +++ b/src/js/store/SubscribeMapList.js @@ -1,86 +1,86 @@ -import { Subscribable } from "./Subscribable.js"; -import { SubscribeValue } from "./SubscribeValue.js"; +import {Subscribable} from "./Subscribable.js" +import {SubscribeValue} from "./SubscribeValue.js" class SubscribeMapList extends Subscribable { - constructor(inner) { - super(); - this.inner = inner; - Object.assign(this.events, { - addItem: [], - deleteItem: [], - editItem: [], - changeItem: [], - askAdd: [], - }); - Object.assign(this.eventDeps, { - addItem: ["changeItem"], - deleteItem: ["changeItem"], - editItem: ["changeItem"], - changeItem: [], - askAdd: [], - }); - this.map = new Map(); - this.list = []; - } + constructor(inner) { + super() + this.inner = inner + Object.assign(this.events, { + addItem: [], + deleteItem: [], + editItem: [], + changeItem: [], + askAdd: [] + }) + Object.assign(this.eventDeps, { + addItem: ["changeItem"], + deleteItem: ["changeItem"], + editItem: ["changeItem"], + changeItem: [], + askAdd: [] + }) + this.map = new Map() + this.list = [] + } - has(key) { - return this.map.has(key) && this.map.get(key).exists(); - } + has(key) { + return this.map.has(key) && this.map.get(key).exists() + } - get(key) { - if (this.map.has(key)) { - return this.map.get(key); - } else { - const item = new this.inner(); - this.map.set(key, item); - return item; - } - } + get(key) { + if (this.map.has(key)) { + return this.map.get(key) + } else { + const item = new this.inner() + this.map.set(key, item) + return item + } + } - forEach(f) { - this.list.forEach((key) => f(key, this.get(key))); - } + forEach(f) { + this.list.forEach(key => f(key, this.get(key))) + } - askAdd(key, data) { - this.broadcast("askAdd", { key, data }); - } + askAdd(key, data) { + this.broadcast("askAdd", {key, data}) + } - addStart(key, value) { - this._add(key, value, true); - } + addStart(key, value) { + this._add(key, value, true) + } - addEnd(key, value) { - this._add(key, value, false); - } + addEnd(key, value) { + this._add(key, value, false) + } - sort() { - this.list.sort((a, b) => { - const orderA = this.map.get(a).value().order; - const orderB = this.map.get(b).value().order; - return orderA - orderB; - }); - this.broadcast("changeItem"); - } + sort() { + this.list.sort((a, b) => { + const orderA = this.map.get(a).value().order + const orderB = this.map.get(b).value().order + return orderA - orderB + }) + this.broadcast("changeItem") + } - _add(key, value, start) { - let s; - if (this.map.has(key)) { - const exists = this.map.get(key).exists(); - s = this.map.get(key).set(value); - if (exists) { - this.broadcast("editItem", key); - } else { - this.broadcast("addItem", key); - } - } else { - s = new this.inner().set(value); - this.map.set(key, s); - if (start) this.list.unshift(key); - else this.list.push(key); - this.broadcast("addItem", key); - } - return s; - } + _add(key, value, start) { + let s + if (this.map.has(key)) { + const exists = this.map.get(key).exists() + s = this.map.get(key).set(value) + if (exists) { + this.broadcast("editItem", key) + } else { + this.broadcast("addItem", key) + } + } else { + s = new this.inner().set(value) + this.map.set(key, s) + if (start) this.list.unshift(key) + else this.list.push(key) + this.broadcast("addItem", key) + } + return s + } } -export { SubscribeMapList }; +export {SubscribeMapList} diff --git a/src/js/store/SubscribeSet.js b/src/js/store/SubscribeSet.js index 34d46ee..2cbdaa3 100644 --- a/src/js/store/SubscribeSet.js +++ b/src/js/store/SubscribeSet.js @@ -1,50 +1,50 @@ -import { Subscribable } from "./Subscribable.js"; +import {Subscribable} from "./Subscribable.js" class SubscribeSet extends Subscribable { - constructor() { - super(); - Object.assign(this.events, { - addItem: [], - deleteItem: [], - changeItem: [], - askAdd: [], - }); - Object.assign(this.eventDeps, { - addItem: ["changeItem"], - deleteItem: ["changeItem"], - changeItem: [], - askAdd: [], - }); - this.set = new Set(); - } + constructor() { + super() + Object.assign(this.events, { + addItem: [], + deleteItem: [], + changeItem: [], + askAdd: [] + }) + Object.assign(this.eventDeps, { + addItem: ["changeItem"], + deleteItem: ["changeItem"], + changeItem: [], + askAdd: [] + }) + this.set = new Set() + } - has(key) { - return this.set.has(key); - } + has(key) { + return this.set.has(key) + } - forEach(f) { - for (const key of this.set.keys()) { - f(key); - } - } + forEach(f) { + for (const key of this.set.keys()) { + f(key) + } + } - askAdd(key) { - this.broadcast("askAdd", key); - } + askAdd(key) { + this.broadcast("askAdd", key) + } - add(key) { - if (!this.set.has(key)) { - this.set.add(key); - this.broadcast("addItem", key); - } - } + add(key) { + if (!this.set.has(key)) { + this.set.add(key) + this.broadcast("addItem", key) + } + } - delete(key) { - if (this.set.has(key)) { - this.set.delete(key); - this.broadcast("deleteItem", key); - } - } + delete(key) { + if (this.set.has(key)) { + this.set.delete(key) + this.broadcast("deleteItem", key) + } + } } -export { SubscribeSet }; +export {SubscribeSet} diff --git a/src/js/store/SubscribeValue.js b/src/js/store/SubscribeValue.js index 4d51c5d..16e5b88 100644 --- a/src/js/store/SubscribeValue.js +++ b/src/js/store/SubscribeValue.js @@ -1,47 +1,47 @@ -import { Subscribable } from "./Subscribable.js"; +import {Subscribable} from "./Subscribable.js" class SubscribeValue extends Subscribable { - constructor() { - super(); - this.hasData = false; - this.data = null; - } + constructor() { + super() + this.hasData = false + this.data = null + } - exists() { - return this.hasData; - } + exists() { + return this.hasData + } - value() { - if (this.hasData) return this.data; - else return null; - } + value() { + if (this.hasData) return this.data + else return null + } - set(data) { - const exists = this.exists(); - this.data = data; - this.hasData = true; - if (exists) { - this.broadcast("editSelf", this.data); - } else { - this.broadcast("addSelf", this.data); - } - return this; - } + set(data) { + const exists = this.exists() + this.data = data + this.hasData = true + if (exists) { + this.broadcast("editSelf", this.data) + } else { + this.broadcast("addSelf", this.data) + } + return this + } - edit(f) { - if (this.exists()) { - f(this.data); - this.set(this.data); - } else { - throw new Error("Tried to edit a SubscribeValue that had no value"); - } - } + edit(f) { + if (this.exists()) { + f(this.data) + this.set(this.data) + } else { + throw new Error("Tried to edit a SubscribeValue that had no value") + } + } - delete() { - this.hasData = false; - this.broadcast("removeSelf"); - return this; - } + delete() { + this.hasData = false + this.broadcast("removeSelf") + return this + } } -export { SubscribeValue }; +export {SubscribeValue} diff --git a/src/js/store/store.js b/src/js/store/store.js index bd90ff1..716f23a 100644 --- a/src/js/store/store.js +++ b/src/js/store/store.js @@ -1,17 +1,17 @@ -import { Subscribable } from "./Subscribable.js"; -import { SubscribeMapList } from "./SubscribeMapList.js"; -import { SubscribeSet } from "./SubscribeSet.js"; -import { SubscribeValue } from "./SubscribeValue.js"; +import {Subscribable} from "./Subscribable.js" +import {SubscribeMapList} from "./SubscribeMapList.js" +import {SubscribeSet} from "./SubscribeSet.js" +import {SubscribeValue} from "./SubscribeValue.js" const store = { - groups: new SubscribeMapList(SubscribeValue), - rooms: new SubscribeMapList(SubscribeValue), - directs: new SubscribeSet(), - activeGroup: new SubscribeValue(), - activeRoom: new SubscribeValue(), - newEvents: new Subscribable(), -}; + groups: new SubscribeMapList(SubscribeValue), + rooms: new SubscribeMapList(SubscribeValue), + directs: new SubscribeSet(), + activeGroup: new SubscribeValue(), + activeRoom: new SubscribeValue(), + newEvents: new Subscribable() +} -window.store = store; +window.store = store -export { store }; +export {store} diff --git a/src/js/sync/sync.js b/src/js/sync/sync.js index 3a93d8b..e5a8389 100644 --- a/src/js/sync/sync.js +++ b/src/js/sync/sync.js @@ -1,139 +1,130 @@ -import { store } from "../store/store.js"; -import * as lsm from "../lsm.js"; +import {store} from "../store/store.js" +import * as lsm from "../lsm.js" -let lastBatch = null; +let lastBatch = null function resolveMxc(url, size, method) { - const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1); - if (size && method) { - return `${lsm.get( - "domain" - )}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`; - } else { - return `${lsm.get("domain")}/_matrix/media/r0/download/${server}/${id}`; - } + const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1) + if (size && method) { + return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}` + } else { + return `${lsm.get("domain")}/_matrix/media/r0/download/${server}/${id}` + } } + function sync() { - const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/sync`); - url.searchParams.append("access_token", lsm.get("access_token")); - const filter = { - room: { - // pulling more from the timeline massively increases download size - timeline: { - limit: 5, - }, - // members are not currently needed - state: { - lazy_load_members: true, - }, - }, - presence: { - // presence is not implemented, ignore it - types: [], - }, - }; - url.searchParams.append("filter", JSON.stringify(filter)); - url.searchParams.append("timeout", 20000); - if (lastBatch) { - url.searchParams.append("since", lastBatch); - } - return fetch(url.toString()) - .then((res) => res.json()) - .then((root) => { - lastBatch = root.next_batch; - return root; - }); + const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/sync`) + url.searchParams.append("access_token", lsm.get("access_token")) + const filter = { + room: { + // pulling more from the timeline massively increases download size + timeline: { + limit: 5 + }, + // members are not currently needed + state: { + lazy_load_members: true + } + }, + presence: { + // presence is not implemented, ignore it + types: [] + } + } + url.searchParams.append("filter", JSON.stringify(filter)) + url.searchParams.append("timeout", 20000) + if (lastBatch) { + url.searchParams.append("since", lastBatch) + } + return fetch(url.toString()).then(res => res.json()).then(root => { + lastBatch = root.next_batch + return root + }) } function manageSync(root) { - try { - let newEvents = false; + try { + let newEvents = false - // set up directs - const directs = root.account_data.events.find((e) => e.type === "m.direct"); - if (directs) { - Object.values(directs.content).forEach((ids) => { - ids.forEach((id) => store.directs.add(id)); - }); - } + // set up directs + const directs = root.account_data.events.find(e => e.type === "m.direct") + if (directs) { + Object.values(directs.content).forEach(ids => { + ids.forEach(id => store.directs.add(id)) + }) + } - // set up rooms - Object.entries(root.rooms.join).forEach(([id, room]) => { - if (!store.rooms.has(id)) { - store.rooms.askAdd(id, room); - } - const timeline = store.rooms.get(id).value().timeline; - if (room.timeline.events.length) newEvents = true; - timeline.updateEvents(room.timeline.events); - }); + // set up rooms + Object.entries(root.rooms.join).forEach(([id, room]) => { + if (!store.rooms.has(id)) { + store.rooms.askAdd(id, room) + } + const timeline = store.rooms.get(id).value().timeline + if (room.timeline.events.length) newEvents = true + timeline.updateEvents(room.timeline.events) + }) - // set up groups - Promise.all( - Object.keys(root.groups.join).map((id) => { - if (!store.groups.has(id)) { - return Promise.all( - ["profile", "rooms"].map((path) => { - const url = new URL( - `${lsm.get("domain")}/_matrix/client/r0/groups/${id}/${path}` - ); - url.searchParams.append("access_token", lsm.get("access_token")); - return fetch(url.toString()).then((res) => res.json()); - }) - ).then(([profile, rooms]) => { - rooms = rooms.chunk; - let order = 999; - let orderEvent = root.account_data.events.find( - (e) => e.type === "im.vector.web.tag_ordering" - ); - if (orderEvent) { - if (orderEvent.content.tags.includes(id)) { - order = orderEvent.content.tags.indexOf(id); - } - } - const data = { - name: profile.name, - icon: resolveMxc(profile.avatar_url, 96, "crop"), - order, - }; - store.groups.askAdd(id, data); - rooms.forEach((groupRoom) => { - if (store.rooms.has(groupRoom.room_id)) { - store.rooms.get(groupRoom.room_id).value().setGroup(id); - } - }); - }); - } - }) - ).then(() => { - store.rooms.sort(); - }); - if (newEvents) store.newEvents.broadcast("changeSelf"); - } catch (e) { - console.error(root); - throw e; - } + // set up groups + Promise.all( + Object.keys(root.groups.join).map(id => { + if (!store.groups.has(id)) { + return Promise.all(["profile", "rooms"].map(path => { + const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/groups/${id}/${path}`) + url.searchParams.append("access_token", lsm.get("access_token")) + return fetch(url.toString()).then(res => res.json()) + })).then(([profile, rooms]) => { + rooms = rooms.chunk + let order = 999 + let orderEvent = root.account_data.events.find(e => e.type === "im.vector.web.tag_ordering") + if (orderEvent) { + if (orderEvent.content.tags.includes(id)) { + order = orderEvent.content.tags.indexOf(id) + } + } + const data = { + name: profile.name, + icon: resolveMxc(profile.avatar_url, 96, "crop"), + order + } + store.groups.askAdd(id, data) + rooms.forEach(groupRoom => { + if (store.rooms.has(groupRoom.room_id)) { + store.rooms.get(groupRoom.room_id).value().setGroup(id) + } + }) + }) + } + }) + ).then(() => { + store.rooms.sort() + }) + if (newEvents) store.newEvents.broadcast("changeSelf") + } catch (e) { + console.error(root) + throw e + } } function syncLoop() { - return sync().then(manageSync).then(syncLoop); + return sync().then(manageSync).then(syncLoop) } -[ - { - id: "directs", - name: "Directs", - icon: "/static/directs.svg", - order: -2, - }, - { - id: "channels", - name: "Channels", - icon: "/static/channels.svg", - order: -1, - }, -].forEach((data) => store.groups.askAdd(data.id, data)); +;[ + { + id: "directs", + name: "Directs", + icon: "/static/directs.svg", + order: -2 + }, + { + id: "channels", + name: "Channels", + icon: "/static/channels.svg", + order: -1 + } +].forEach(data => store.groups.askAdd(data.id, data)) -store.activeGroup.set(store.groups.get("directs").value()); +store.activeGroup.set(store.groups.get("directs").value()) -syncLoop(); +syncLoop() diff --git a/src/login.pug b/src/login.pug index 9ff0f39..105b6bd 100644 --- a/src/login.pug +++ b/src/login.pug @@ -1,36 +1,21 @@ doctype html html - head - meta(charset="utf-8") - link(rel="stylesheet", type="text/css", href=getStatic('/sass/main.sass')) - title Carbon - body - main.main - form - div - label(for="login") Username - input#login( - type="text", - name="login", - autocomplete="username", - placeholder="example:matrix.org", - required - ) - div - label(for="password") Password - input#password( - type="text", - name="password", - autocomplete="current-password", - required - ) - div - label(for="homeserver") Homeserver - input#homeserver( - type="text", - name="homeserver", - value="matrix.org", - required - ) - div - input(type="submit", value="Login") + head + meta(charset="utf-8") + link(rel="stylesheet" type="text/css" href=getStatic("/sass/main.sass")) + title Carbon + body + main.main + form + div + label(for="login") Username + input(type="text" name="login" autocomplete="username" placeholder="example:matrix.org" required)#login + div + label(for="password") Password + input(type="text" name="password" autocomplete="current-password" required)#password + div + + label(for="homeserver") Homeserver + input(type="text" name="homeserver" value="matrix.org" required)#homeserver + div + input(type="submit" value="Login")