parent
d7454ddc1c
commit
f9662e31a2
24 changed files with 1251 additions and 1392 deletions
|
@ -1 +0,0 @@
|
|||
.gitignore
|
|
@ -1 +0,0 @@
|
|||
{}
|
451
build.js
451
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")
|
||||
})()
|
||||
|
|
|
@ -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
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"
|
||||
}
|
||||
|
|
232
spec.js
232
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"
|
||||
}
|
||||
]
|
||||
|
|
31
src/home.pug
31
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
|
|
@ -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}
|
||||
|
|
246
src/js/basic.js
246
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)
|
||||