forked from cadence/Carbon
		
	Groups and rooms list first draft
This commit is contained in:
		
							parent
							
								
									36430999f0
								
							
						
					
					
						commit
						da1f63fc8d
					
				
					 16 changed files with 917 additions and 0 deletions
				
			
		
							
								
								
									
										231
									
								
								build.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								build.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,231 @@
 | 
			
		|||
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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
					}
 | 
			
		||||
					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: ["require", "console", "URLSearchParams", "L"],
 | 
			
		||||
		strict: "global",
 | 
			
		||||
		browser: true
 | 
			
		||||
	})
 | 
			
		||||
	const result = hint.data()
 | 
			
		||||
	if (result.errors && result.errors.length) {
 | 
			
		||||
		for (const error of result.errors) {
 | 
			
		||||
			if (error.evidence) {
 | 
			
		||||
				const text = error.evidence.replace(/\t/g, "    ")
 | 
			
		||||
				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)
 | 
			
		||||
				))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		console.log(`hint: ${chalk.cyan(result.errors.length+" 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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|=).*?\?>/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))
 | 
			
		||||
	])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
;(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")
 | 
			
		||||
})()
 | 
			
		||||
							
								
								
									
										78
									
								
								build/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								build/index.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="static/main.css?static=13628a6e3d">
 | 
			
		||||
    <script type="module" src="static/groups.js?static=18b23cbd17"></script>
 | 
			
		||||
    <title>Carbon</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <main class="main">
 | 
			
		||||
      <div class="c-groups">
 | 
			
		||||
        <div class="c-groups__display" id="c-groups">
 | 
			
		||||
          <div class="c-groups__container">
 | 
			
		||||
            <div class="c-group">
 | 
			
		||||
              <div class="c-group__icon"></div>
 | 
			
		||||
              <div class="c-group__name">Directs</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="c-group">
 | 
			
		||||
              <div class="c-group__icon"></div>
 | 
			
		||||
              <div class="c-group__name">Channels</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="c-group">
 | 
			
		||||
              <div class="c-group__icon"></div>
 | 
			
		||||
              <div class="c-group__name">Fediverse Drama Museum</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="c-group">
 | 
			
		||||
              <div class="c-group__icon"></div>
 | 
			
		||||
              <div class="c-group__name">Epicord</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="c-group">
 | 
			
		||||
              <div class="c-group__icon"></div>
 | 
			
		||||
              <div class="c-group__name">Invidious</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="c-rooms" id="c-rooms">
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">Carbon brainstorming</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">riley</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">BadAtNames</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">lepton</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">cockandball</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">Bibliogram</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">Monsters Inc Debate Hall</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">DRB clan</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="c-room">
 | 
			
		||||
          <div class="c-room__icon"></div>
 | 
			
		||||
          <div class="c-room__name">mettaton simp zone</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="c-chat"></div>
 | 
			
		||||
    </main>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										148
									
								
								build/static/basic.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								build/static/basic.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,148 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Shortcut for querySelector.
 | 
			
		||||
 * @template {HTMLElement} T
 | 
			
		||||
 * @returns {T}
 | 
			
		||||
 */
 | 
			
		||||
const q = s => document.querySelector(s);
 | 
			
		||||
/**
 | 
			
		||||
 * Shortcut for querySelectorAll.
 | 
			
		||||
 * @template {HTMLElement} T
 | 
			
		||||
 * @returns {T[]}
 | 
			
		||||
 */
 | 
			
		||||
const qa = s => document.querySelectorAll(s);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An easier, chainable, object-oriented way to create and update elements
 | 
			
		||||
 * and children according to related data. Subclass ElemJS to create useful,
 | 
			
		||||
 * advanced data managers, or just use it inline to quickly make a custom element.
 | 
			
		||||
 * 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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Shortcut for `new ElemJS`. */
 | 
			
		||||
function ejs(tag) {
 | 
			
		||||
	return new ElemJS(tag);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {q, qa, ElemJS, ejs}
 | 
			
		||||
							
								
								
									
										15
									
								
								build/static/groups.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								build/static/groups.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
import {q} from "./basic.js"
 | 
			
		||||
 | 
			
		||||
let state = "CLOSED"
 | 
			
		||||
 | 
			
		||||
const groups = q("#c-groups")
 | 
			
		||||
const rooms = q("#c-rooms")
 | 
			
		||||
 | 
			
		||||
groups.addEventListener("click", () => {
 | 
			
		||||
	console.log("hello", groups)
 | 
			
		||||
	groups.classList.add("c-groups__display--closed")
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
rooms.addEventListener("mouseout", () => {
 | 
			
		||||
	groups.classList.remove("c-groups__display--closed")
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										99
									
								
								build/static/main.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								build/static/main.css
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,99 @@
 | 
			
		|||
@font-face {
 | 
			
		||||
	font-family: Whitney;
 | 
			
		||||
	font-weight: 500;
 | 
			
		||||
	src: url(/static/whitney-500.woff?static=ba33ed18fe) format("woff2");
 | 
			
		||||
}
 | 
			
		||||
body {
 | 
			
		||||
	font-family: sans-serif;
 | 
			
		||||
	background-color: #36393e;
 | 
			
		||||
	color: #ddd;
 | 
			
		||||
	font-size: 24px;
 | 
			
		||||
	font-family: Whitney;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	height: 100vh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main {
 | 
			
		||||
	height: 100vh;
 | 
			
		||||
	display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-rooms {
 | 
			
		||||
	background-color: #2f3135;
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
	width: 240px;
 | 
			
		||||
	font-size: 20px;
 | 
			
		||||
	overflow-y: scroll;
 | 
			
		||||
	scrollbar-width: thin;
 | 
			
		||||
	scrollbar-color: #42454a #2f3135;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-room {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
.c-room:hover {
 | 
			
		||||
	background-color: #393c42;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
}
 | 
			
		||||
.c-room__icon {
 | 
			
		||||
	width: 36px;
 | 
			
		||||
	height: 36px;
 | 
			
		||||
	background-color: #bbb;
 | 
			
		||||
	margin-right: 8px;
 | 
			
		||||
	border-radius: 50%;
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
}
 | 
			
		||||
.c-room__name {
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-groups {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	width: 80px;
 | 
			
		||||
}
 | 
			
		||||
.c-groups__display {
 | 
			
		||||
	background-color: #202224;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	width: 80px;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	right: 0;
 | 
			
		||||
}
 | 
			
		||||
.c-groups__display:not(.c-groups__display--closed):hover {
 | 
			
		||||
	width: 300px;
 | 
			
		||||
}
 | 
			
		||||
.c-groups__container {
 | 
			
		||||
	width: 300px;
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c-group {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	padding: 4px 8px;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
}
 | 
			
		||||
.c-group:hover {
 | 
			
		||||
	background-color: #2f3135;
 | 
			
		||||
}
 | 
			
		||||
.c-group__icon {
 | 
			
		||||
	width: 48px;
 | 
			
		||||
	height: 48px;
 | 
			
		||||
	background-color: #999;
 | 
			
		||||
	border-radius: 50%;
 | 
			
		||||
	margin-right: 16px;
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
}
 | 
			
		||||
.c-group__name {
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								build/static/whitney-500.woff
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								build/static/whitney-500.woff
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										32
									
								
								spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								spec.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
module.exports = [
 | 
			
		||||
	{
 | 
			
		||||
		type: "file",
 | 
			
		||||
		source: "/assets/fonts/whitney-500.woff",
 | 
			
		||||
		target: "/static/whitney-500.woff"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		type: "file",
 | 
			
		||||
		source: "/js/basic.js",
 | 
			
		||||
		target: "/static/basic.js"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		type: "file",
 | 
			
		||||
		source: "/js/groups.js",
 | 
			
		||||
		target: "/static/groups.js"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		type: "file",
 | 
			
		||||
		source: "/assets/fonts/whitney-500.woff",
 | 
			
		||||
		target: "/static/whitney-500.woff"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		type: "sass",
 | 
			
		||||
		source: "/sass/main.sass",
 | 
			
		||||
		target: "/static/main.css"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		type: "pug",
 | 
			
		||||
		source: "/home.pug",
 | 
			
		||||
		target: "/index.html"
 | 
			
		||||
	}
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/fonts/whitney-500.woff
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/fonts/whitney-500.woff
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										38
									
								
								src/home.pug
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/home.pug
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
mixin group(name)
 | 
			
		||||
  .c-group
 | 
			
		||||
    .c-group__icon
 | 
			
		||||
    .c-group__name= name
 | 
			
		||||
 | 
			
		||||
mixin room(name)
 | 
			
		||||
  .c-room
 | 
			
		||||
    .c-room__icon
 | 
			
		||||
    .c-room__name= name
 | 
			
		||||
 | 
			
		||||
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"))
 | 
			
		||||
    title Carbon
 | 
			
		||||
  body
 | 
			
		||||
    main.main
 | 
			
		||||
      .c-groups
 | 
			
		||||
        .c-groups__display#c-groups
 | 
			
		||||
          .c-groups__container
 | 
			
		||||
            +group("Directs")
 | 
			
		||||
            +group("Channels")
 | 
			
		||||
            +group("Fediverse Drama Museum")
 | 
			
		||||
            +group("Epicord")
 | 
			
		||||
            +group("Invidious")
 | 
			
		||||
      .c-rooms#c-rooms
 | 
			
		||||
        +room("Carbon brainstorming")
 | 
			
		||||
        +room("riley")
 | 
			
		||||
        +room("BadAtNames")
 | 
			
		||||
        +room("lepton")
 | 
			
		||||
        +room("cockandball")
 | 
			
		||||
        +room("Bibliogram")
 | 
			
		||||
        +room("Monsters Inc Debate Hall")
 | 
			
		||||
        +room("DRB clan")
 | 
			
		||||
        +room("mettaton simp zone")
 | 
			
		||||
      .c-chat
 | 
			
		||||
							
								
								
									
										148
									
								
								src/js/basic.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								src/js/basic.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,148 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Shortcut for querySelector.
 | 
			
		||||
 * @template {HTMLElement} T
 | 
			
		||||
 * @returns {T}
 | 
			
		||||
 */
 | 
			
		||||
const q = s => document.querySelector(s);
 | 
			
		||||
/**
 | 
			
		||||
 * Shortcut for querySelectorAll.
 | 
			
		||||
 * @template {HTMLElement} T
 | 
			
		||||
 * @returns {T[]}
 | 
			
		||||
 */
 | 
			
		||||
const qa = s => document.querySelectorAll(s);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An easier, chainable, object-oriented way to create and update elements
 | 
			
		||||
 * and children according to related data. Subclass ElemJS to create useful,
 | 
			
		||||
 * advanced data managers, or just use it inline to quickly make a custom element.
 | 
			
		||||
 * 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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Shortcut for `new ElemJS`. */
 | 
			
		||||
function ejs(tag) {
 | 
			
		||||
	return new ElemJS(tag);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {q, qa, ElemJS, ejs}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/js/groups.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/js/groups.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
import {q} from "./basic.js"
 | 
			
		||||
 | 
			
		||||
let state = "CLOSED"
 | 
			
		||||
 | 
			
		||||
const groups = q("#c-groups")
 | 
			
		||||
const rooms = q("#c-rooms")
 | 
			
		||||
 | 
			
		||||
groups.addEventListener("click", () => {
 | 
			
		||||
	console.log("hello", groups)
 | 
			
		||||
	groups.classList.add("c-groups__display--closed")
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
rooms.addEventListener("mouseout", () => {
 | 
			
		||||
	groups.classList.remove("c-groups__display--closed")
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										17
									
								
								src/sass/base.sass
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/sass/base.sass
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
@font-face
 | 
			
		||||
  font-family: Whitney
 | 
			
		||||
  font-weight: 500
 | 
			
		||||
  src: url(static("/assets/fonts/whitney-500.woff")) format("woff2")
 | 
			
		||||
 | 
			
		||||
body
 | 
			
		||||
  font-family: sans-serif
 | 
			
		||||
  background-color: #36393e
 | 
			
		||||
  color: #ddd
 | 
			
		||||
  font-size: 24px
 | 
			
		||||
  font-family: Whitney
 | 
			
		||||
  margin: 0
 | 
			
		||||
  height: 100vh
 | 
			
		||||
 | 
			
		||||
.main
 | 
			
		||||
  height: 100vh
 | 
			
		||||
  display: flex
 | 
			
		||||
							
								
								
									
										5
									
								
								src/sass/colors.sass
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/sass/colors.sass
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
$dark: #36393e
 | 
			
		||||
$darker: #2f3135
 | 
			
		||||
$darkest: #202224
 | 
			
		||||
$mild: #393c42
 | 
			
		||||
$milder: #42454a
 | 
			
		||||
							
								
								
									
										51
									
								
								src/sass/components/groups.sass
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/sass/components/groups.sass
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
@use "../colors" as c
 | 
			
		||||
@use "./rooms" as rooms
 | 
			
		||||
 | 
			
		||||
$icon-size: 48px
 | 
			
		||||
$icon-padding: 8px
 | 
			
		||||
$base-width: $icon-size + $icon_padding * 4
 | 
			
		||||
$out-width: $base-width + rooms.$list-width - 20px
 | 
			
		||||
 | 
			
		||||
.c-groups
 | 
			
		||||
  position: relative
 | 
			
		||||
  width: $base-width
 | 
			
		||||
 | 
			
		||||
  &__display
 | 
			
		||||
    background-color: c.$darkest
 | 
			
		||||
    overflow: hidden
 | 
			
		||||
    width: $base-width
 | 
			
		||||
    position: absolute
 | 
			
		||||
    left: 0
 | 
			
		||||
    top: 0
 | 
			
		||||
    bottom: 0
 | 
			
		||||
    right: 0
 | 
			
		||||
 | 
			
		||||
    &:not(&--closed):hover
 | 
			
		||||
      width: $out-width
 | 
			
		||||
 | 
			
		||||
  &__container
 | 
			
		||||
    width: $out-width
 | 
			
		||||
    padding: $icon-padding
 | 
			
		||||
 | 
			
		||||
.c-group
 | 
			
		||||
  display: flex
 | 
			
		||||
  align-items: center
 | 
			
		||||
  padding: $icon-padding / 2 $icon-padding
 | 
			
		||||
  cursor: pointer
 | 
			
		||||
  border-radius: 8px
 | 
			
		||||
 | 
			
		||||
  &:hover
 | 
			
		||||
    background-color: c.$darker
 | 
			
		||||
 | 
			
		||||
  &__icon
 | 
			
		||||
    width: $icon-size
 | 
			
		||||
    height: $icon-size
 | 
			
		||||
    background-color: #999
 | 
			
		||||
    border-radius: 50%
 | 
			
		||||
    margin-right: $icon-padding * 2
 | 
			
		||||
    flex-shrink: 0
 | 
			
		||||
 | 
			
		||||
  &__name
 | 
			
		||||
    white-space: nowrap
 | 
			
		||||
    overflow: hidden
 | 
			
		||||
    text-overflow: ellipsis
 | 
			
		||||
							
								
								
									
										37
									
								
								src/sass/components/rooms.sass
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/sass/components/rooms.sass
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
@use "../colors" as c
 | 
			
		||||
 | 
			
		||||
$list-width: 240px
 | 
			
		||||
$icon-size: 36px
 | 
			
		||||
$icon-padding: 8px
 | 
			
		||||
 | 
			
		||||
.c-rooms
 | 
			
		||||
  background-color: c.$darker
 | 
			
		||||
  padding: $icon-padding
 | 
			
		||||
  width: $list-width
 | 
			
		||||
  font-size: 20px
 | 
			
		||||
  overflow-y: scroll
 | 
			
		||||
  scrollbar-width: thin
 | 
			
		||||
  scrollbar-color: c.$milder c.$darker
 | 
			
		||||
 | 
			
		||||
.c-room
 | 
			
		||||
  display: flex
 | 
			
		||||
  align-items: center
 | 
			
		||||
  padding: $icon-padding
 | 
			
		||||
  cursor: pointer
 | 
			
		||||
 | 
			
		||||
  &:hover
 | 
			
		||||
    background-color: c.$mild
 | 
			
		||||
    border-radius: 8px
 | 
			
		||||
 | 
			
		||||
  &__icon
 | 
			
		||||
    width: $icon-size
 | 
			
		||||
    height: $icon-size
 | 
			
		||||
    background-color: #bbb
 | 
			
		||||
    margin-right: $icon-padding
 | 
			
		||||
    border-radius: 50%
 | 
			
		||||
    flex-shrink: 0
 | 
			
		||||
 | 
			
		||||
  &__name
 | 
			
		||||
    white-space: nowrap
 | 
			
		||||
    overflow: hidden
 | 
			
		||||
    text-overflow: ellipsis
 | 
			
		||||
							
								
								
									
										3
									
								
								src/sass/main.sass
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/sass/main.sass
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
@use "./base"
 | 
			
		||||
@use "./components/groups"
 | 
			
		||||
@use "./components/rooms"
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue