Revert "Add prettier"

This reverts commit 6ef4552fd0.
pull/6/head
Cadence Ember 2 years ago
parent d7454ddc1c
commit f9662e31a2
Signed by: cadence
GPG Key ID: BC1C2C61CF521B17
  1. 1
      .prettierignore
  2. 1
      .prettierrc.json
  3. 475
      build.js
  4. 14
      jsconfig.json
  5. 17
      package-lock.json
  6. 2
      package.json
  7. 232
      spec.js
  8. 31
      src/home.pug
  9. 20
      src/js/Anchor.js
  10. 269
      src/js/Timeline.js
  11. 276
      src/js/basic.js
  12. 83
      src/js/chat-input.js
  13. 113
      src/js/chat.js
  14. 16
      src/js/groups.js
  15. 8
      src/js/lsm.js
  16. 444
      src/js/room-picker.js
  17. 71
      src/js/store/Subscribable.js
  18. 68
      src/js/store/SubscribeMap.js
  19. 148
      src/js/store/SubscribeMapList.js
  20. 82
      src/js/store/SubscribeSet.js
  21. 76
      src/js/store/SubscribeValue.js
  22. 26
      src/js/store/store.js
  23. 231
      src/js/sync/sync.js
  24. 53
      src/login.pug

@ -1 +0,0 @@
.gitignore

@ -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;
process.chdir(pj(__dirname, "src"));
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 spec = require("./spec.js");
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"))
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 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 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)}`;
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)),
]);
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 filenameWithQuery = `${targetPath}?static=${hash(compiled.code)}`
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))
])
}
(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);
}
}
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());
});
console.log(chalk.green("Build complete.") + "\n\n------------\n");
})();
;(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)
}
}
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())
})
console.log(chalk.green("Build complete.") + "\n\n------------\n")
})()

@ -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
}
}

17
package-lock.json generated

@ -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",

@ -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"
}

@ -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"
}
]

@ -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

@ -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}

@ -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}

@ -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 = [];
}
/** 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;
}
/** 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 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 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;
}
/** 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;
}
/** 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;
}
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;