From 7b563f5c31d839f6db54ceabb770bc89ea0a1b47 Mon Sep 17 00:00:00 2001 From: roxwize Date: Sun, 17 Nov 2024 14:16:27 -0500 Subject: [PATCH] i dont really know what i added Signed-off-by: roxwize --- db/schema.ts | 9 + drizzle/0006_lame_grey_gargoyle.sql | 14 ++ drizzle/meta/0006_snapshot.json | 356 ++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + main.ts | 133 +++-------- routes/login.ts | 2 + routes/updates.ts | 161 +++++++++++++ routes/users.ts | 15 ++ routes/util.ts | 18 +- static/css/dashboard.css | 76 +++--- static/css/main.css | 34 +++ static/img/down.svg | 3 + static/img/downdown.svg | 3 + static/img/line.svg | 3 + static/img/logo.svg | 6 +- static/img/up.svg | 3 + static/img/upup.svg | 3 + static/moods.txt | 2 +- views/_feed.pug | 14 ++ views/dashboard.pug | 17 +- views/index.pug | 10 + views/journal.pug | 48 ++++ views/site.pug | 6 +- views/user.pug | 5 +- 24 files changed, 797 insertions(+), 151 deletions(-) create mode 100644 drizzle/0006_lame_grey_gargoyle.sql create mode 100644 drizzle/meta/0006_snapshot.json create mode 100644 routes/updates.ts create mode 100644 static/img/down.svg create mode 100644 static/img/downdown.svg create mode 100644 static/img/line.svg create mode 100644 static/img/up.svg create mode 100644 static/img/upup.svg create mode 100644 views/_feed.pug create mode 100644 views/journal.pug diff --git a/db/schema.ts b/db/schema.ts index 4266e0f..5ad2c2f 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -27,6 +27,15 @@ export const updates = pgTable("updates", { date: timestamp().notNull() }); +export const journalEntries = pgTable("journal_entries", { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + user: integer().references(() => users.id, { onDelete: "cascade" }).notNull(), + moodChange: integer("mood-change").default(0).notNull(), + entry: varchar({ length: 4096 }).default("").notNull(), + visibility: integer().default(1).notNull(), + date: timestamp().notNull() +}); + export const profiles = pgTable("profiles", { user: integer() .references(() => users.id, { onDelete: "cascade" }) diff --git a/drizzle/0006_lame_grey_gargoyle.sql b/drizzle/0006_lame_grey_gargoyle.sql new file mode 100644 index 0000000..1b594c9 --- /dev/null +++ b/drizzle/0006_lame_grey_gargoyle.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS "journal_entries" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "journal_entries_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "user" integer NOT NULL, + "mood-change" integer DEFAULT 0 NOT NULL, + "entry" varchar(4096) DEFAULT '' NOT NULL, + "visibility" integer DEFAULT 1 NOT NULL, + "date" timestamp NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "journal_entries" ADD CONSTRAINT "journal_entries_user_users_id_fk" FOREIGN KEY ("user") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..f775bae --- /dev/null +++ b/drizzle/meta/0006_snapshot.json @@ -0,0 +1,356 @@ +{ + "id": "73a239ee-a945-48e0-a021-d415815a830b", + "prevId": "f8ae31ec-68e9-4857-93b2-abd7f834e580", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.follows": { + "name": "follows", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "follower_id": { + "name": "follower_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "follows_user_id_users_id_fk": { + "name": "follows_user_id_users_id_fk", + "tableFrom": "follows", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "follows_follower_id_users_id_fk": { + "name": "follows_follower_id_users_id_fk", + "tableFrom": "follows", + "tableTo": "users", + "columnsFrom": [ + "follower_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "follows_user_id_follower_id_pk": { + "name": "follows_user_id_follower_id_pk", + "columns": [ + "user_id", + "follower_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.journal_entries": { + "name": "journal_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "journal_entries_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "user": { + "name": "user", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mood-change": { + "name": "mood-change", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entry": { + "name": "entry", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "visibility": { + "name": "visibility", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "journal_entries_user_users_id_fk": { + "name": "journal_entries_user_users_id_fk", + "tableFrom": "journal_entries", + "tableTo": "users", + "columnsFrom": [ + "user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.profiles": { + "name": "profiles", + "schema": "", + "columns": { + "user": { + "name": "user", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "bio": { + "name": "bio", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "website": { + "name": "website", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "profiles_user_users_id_fk": { + "name": "profiles_user_users_id_fk", + "tableFrom": "profiles", + "tableTo": "users", + "columnsFrom": [ + "user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.updates": { + "name": "updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "updates_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "user": { + "name": "user", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mood": { + "name": "mood", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "description": { + "name": "description", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "updates_user_users_id_fk": { + "name": "updates_user_users_id_fk", + "tableFrom": "updates", + "tableTo": "users", + "columnsFrom": [ + "user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "users_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(26)", + "primaryKey": false, + "notNull": true + }, + "pass": { + "name": "pass", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "registered": { + "name": "registered", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "moderator": { + "name": "moderator", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "users_name_unique": { + "name": "users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index eeda2ed..198d197 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -43,6 +43,13 @@ "when": 1731393095652, "tag": "0005_magical_swarm", "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1731868543611, + "tag": "0006_lame_grey_gargoyle", + "breakpoints": true } ] } \ No newline at end of file diff --git a/main.ts b/main.ts index be0773a..fff7e36 100644 --- a/main.ts +++ b/main.ts @@ -1,5 +1,5 @@ //! TODO: There is like no error checking in queries at all -import type { Response, Request } from "express"; +//! TODO: Also no API? seriously? also when a form errors it just redirects with a 301 like everything is okay when it ISNT and it NEVER HAS BEEN import "dotenv/config"; import dayjs from "dayjs"; @@ -12,19 +12,20 @@ import connectPgSimple from "connect-pg-simple"; import flash from "connect-flash"; import { drizzle } from "drizzle-orm/node-postgres"; -import { follows, profiles, updates, users } from "./db/schema.js"; -import { and, desc, eq } from "drizzle-orm"; +import { updates, users } from "./db/schema.js"; +import { desc, eq } from "drizzle-orm"; // routes import loginRoutes from "./routes/login.js"; import userRoutes from "./routes/users.js"; -import { getMoods, render } from "./routes/util.js"; +import updateRoutes from "./routes/updates.js"; +import { getMoods, render, setNonce } from "./routes/util.js"; const db = drizzle(process.env.DATABASE_URL!); //! TODO: Make sure SQL queries arent being repeated too much (async () => { - const { moods, moodsSorted } = await getMoods(); + const { moods } = await getMoods(); // setup dayjs dayjs.extend(relativeTime); @@ -43,6 +44,19 @@ const db = drizzle(process.env.DATABASE_URL!); }) ); app.use(flash()); + //== Content Security Policy + app.use((_, res, next) => { + res.setHeader("X-Powered-By", "Lots and lots of milk"); + res.setHeader( + "Content-Security-Policy", + "\ +script-src 'nonce-" + + setNonce() + + "';\ +object-src 'none'; base-uri 'none';" + ); + return next(); + }); app.get("/", async (req, res) => { const upd = db @@ -57,121 +71,38 @@ const db = drizzle(process.env.DATABASE_URL!); .as("upd"); const recentUpdates = await db.select().from(upd).orderBy(desc(upd.date)); - render(db, "index", "Home", res, req, { - users: (await db.select().from(users)).length, - recentUpdates - }); - }); - - await userRoutes(app, db); - - //! -- TEMP DASHBOARD START -- - app.get("/dashboard", async (req, res) => { - if (!req.session["loggedIn"]) { - res.redirect("/login"); - return; - } - const user = ( - await db - .select({ - name: users.name, - bio: profiles.bio, - website: profiles.website //! validate this - }) - .from(users) - .where(eq(users.name, req.session["user"])) - .leftJoin(profiles, eq(users.id, profiles.user)) - )[0]; - - const now = dayjs(); - const moodHistory = ( - await db - .select({ mood: updates.mood, date: updates.date }) - .from(updates) - .where(eq(updates.user, req.session["uid"])) - .orderBy(desc(updates.date)) - .limit(10) - ).map((e) => { - return { mood: moods[e.mood], date: now.to(dayjs(e.date)) }; - }); - - const recentUpdates = ( + const date = dayjs(); + const feedUpdates = ( await db .select({ user: users.name, mood: updates.mood, - desc: updates.description, - date: updates.date + date: updates.date, + desc: updates.description }) .from(updates) - .innerJoin( - follows, - and( - eq(follows.userId, updates.user), - eq(follows.followerId, req.session["uid"]) - ) - ) - .leftJoin(users, eq(updates.user, users.id)) + .innerJoin(users, eq(updates.user, users.id)) + .limit(50) .orderBy(desc(updates.date)) - .limit(25) ).map((e) => { return { user: e.user, mood: moods[e.mood], - desc: e.desc, - date: dayjs().to(dayjs(e.date)) + date: date.to(dayjs(e.date)), + desc: e.desc }; }); - render(db, "dashboard", "Dashboard", res, req, { - user, - moods, - moodsSorted, - moodHistory, + render(db, "index", "Home", res, req, { + users: (await db.select().from(users)).length, recentUpdates, - feed: [] + feedUpdates }); }); - app.post("/update", async (req, res) => { - if (!req.session["loggedIn"]) { - res.redirect("/login"); - return; - } - const moodIndex = moods.indexOf(req.body.mood.trim()); - if (moodIndex === -1) { - req.flash( - "error", - "That mood doesn't exist in the database, WTF are you trying to do??" - ); - res.redirect("/dashboard"); - return; - } - if (req.body.desc.length > 512) { - req.flash( - "error", - "Mood description can't be longer than 512 characters" - ); - res.redirect("/dashboard"); - } - await db - .insert(updates) - // @ts-expect-error - .values({ - user: req.session["uid"], - mood: moodIndex, - description: req.body.desc, - date: new Date(Date.now()) - }); - req.flash("success", "Mood updated!"); - res.redirect("/dashboard"); - }); - //! -- TEMP DASHBOARD END -- - - //! TEMP Also not sanitized like at all - //! Also make sure user isnt logged in before doing this + await userRoutes(app, db); + await updateRoutes(app, db); loginRoutes(app, db); - //! TEMP done app.listen(1337, () => { console.log("Listening on http://127.0.0.1:1337/"); diff --git a/routes/login.ts b/routes/login.ts index 7e9c9fe..40c4cc8 100644 --- a/routes/login.ts +++ b/routes/login.ts @@ -5,6 +5,8 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres"; import { profiles, users } from "../db/schema.js"; import { eq } from "drizzle-orm"; +//! TEMP Also not sanitized like at all +//! Also make sure user isnt logged in before doing this export default function(app: Express, db: NodePgDatabase) { app.get("/register", (req, res) => { if (req.session["loggedIn"]) { diff --git a/routes/updates.ts b/routes/updates.ts new file mode 100644 index 0000000..d1dbeb7 --- /dev/null +++ b/routes/updates.ts @@ -0,0 +1,161 @@ +import { NodePgDatabase } from "drizzle-orm/node-postgres"; +import { Express } from "express"; +import { + follows, + journalEntries, + profiles, + updates, + users +} from "../db/schema.js"; +import { and, desc, eq } from "drizzle-orm"; +import dayjs from "dayjs"; +import { getMoods, render } from "./util.js"; + +export default async function (app: Express, db: NodePgDatabase) { + const { moods, moodsSorted } = await getMoods(); + + // DASHBOARD + app.get("/dashboard", async (req, res) => { + if (!req.session["loggedIn"]) { + res.redirect("/login"); + return; + } + const user = ( + await db + .select({ + name: users.name, + bio: profiles.bio, + website: profiles.website //! validate this + }) + .from(users) + .where(eq(users.name, req.session["user"])) + .leftJoin(profiles, eq(users.id, profiles.user)) + )[0]; + + const now = dayjs(); + const moodHistory = ( + await db + .select({ mood: updates.mood, date: updates.date }) + .from(updates) + .where(eq(updates.user, req.session["uid"])) + .orderBy(desc(updates.date)) + .limit(10) + ).map((e) => { + return { mood: moods[e.mood], date: now.to(dayjs(e.date)) }; + }); + + const recentUpdates = ( + await db + .select({ + user: users.name, + mood: updates.mood, + desc: updates.description, + date: updates.date + }) + .from(updates) + .innerJoin( + follows, + and( + eq(follows.userId, updates.user), + eq(follows.followerId, req.session["uid"]) + ) + ) + .leftJoin(users, eq(updates.user, users.id)) + .orderBy(desc(updates.date)) + .limit(25) + ).map((e) => { + return { + user: e.user, + mood: moods[e.mood], + desc: e.desc, + date: now.to(dayjs(e.date)) + }; + }); + + render(db, "dashboard", "Dashboard", res, req, { + user, + moods, + moodsSorted, + moodHistory, + recentUpdates, + feed: [] + }); + }); + app.post("/update/mood", async (req, res) => { + if (!req.session["loggedIn"]) { + res.redirect("/login"); + return; + } + const moodIndex = moods.indexOf(req.body.mood.trim()); + if (moodIndex === -1) { + req.flash( + "error", + "That mood doesn't exist in the database, WTF are you trying to do??" + ); + res.redirect("/dashboard"); + return; + } + if (req.body.desc.length > 512) { + req.flash( + "error", + "Mood description can't be longer than 512 characters" + ); + res.redirect("/dashboard"); + } + + await db + .insert(updates) + // @ts-expect-error + .values({ + user: req.session["uid"], + mood: moodIndex, + description: req.body.desc, + date: new Date(Date.now()) + }); + req.flash("success", "Mood updated!"); + res.redirect("/dashboard"); + }); + + // JOURNAL + app.get("/journal", async (req, res) => { + render(db, "journal", "Journal", res, req); + }); + app.post("/update/journal", async (req, res) => { + if (!req.session["loggedIn"]) { + res.redirect("/login"); + return; + } + if (req.body.description.length > 4096) { + req.flash("error", "Entry too long!"); + res.redirect("/journal"); + return; + } + + const moodChange = parseInt(req.body.moodDelta); + const visibility = parseInt(req.body.visibility); + if (isNaN(moodChange) || isNaN(visibility)) { + req.flash("error", "One of the values was improperly specified."); + res.redirect("/journal"); + return; + } + + let id: number; + try { + // @ts-expect-error + const entry = await db.insert(journalEntries).values({ + user: req.session["uid"], + moodChange, + visibility, + entry: req.body.description, + date: new Date(Date.now()) + }).returning({ id: journalEntries.id }); + id = entry[0].id; + } catch (err) { + req.flash("error", "Failed to create your entry. Try again later or send these logs to roxwize so she can know what's up:

"+err); + res.redirect("/journal"); + return; + } + req.flash("success", `Your journal entry is now available as #${id}!`); + res.redirect("/journal"); + }); +} diff --git a/routes/users.ts b/routes/users.ts index 14baa4e..ee1a8b6 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -4,6 +4,7 @@ import { follows, profiles, updates, users } from "../db/schema.js"; import { and, desc, eq } from "drizzle-orm"; import { getMoods, render } from "./util.js"; import { PgColumn } from "drizzle-orm/pg-core"; +import dayjs from "dayjs"; export default async function (app: Express, db: NodePgDatabase) { const { moods } = await getMoods(); @@ -57,6 +58,19 @@ export default async function (app: Express, db: NodePgDatabase) { .limit(1) )[0]; + // feed + const now = dayjs(); + const userMoodFeed = (await db + .select({ + mood: updates.mood, + date: updates.date, + desc: updates.description + }) + .from(updates) + .where(eq(updates.user, user.id))).map((e) => { + return { user: user.name, mood: moods[e.mood], date: now.to(dayjs(e.date)), desc: e.desc } + }); + if (!isSelf) { userMood.mood = moods[userMood.mood as number]; } @@ -65,6 +79,7 @@ export default async function (app: Express, db: NodePgDatabase) { user, isSelf, userMood, + userMoodFeed, isFollowing }); }); diff --git a/routes/util.ts b/routes/util.ts index a986951..3edfe4b 100644 --- a/routes/util.ts +++ b/routes/util.ts @@ -4,6 +4,21 @@ import { updates } from "../db/schema.js"; import { desc, eq } from "drizzle-orm"; import fs from "node:fs/promises"; +const nonceChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-_"; +let nonce: string; +export function setNonce() { + nonce = ""; + for (let i = 0; i < 32; i++) + nonce += nonceChars[Math.floor(Math.random() * nonceChars.length)]; + return nonce; +} +export function getNonce() { + if (!nonce) + throw new Error("Nonce doesn't exist"); + return nonce; +} + let moods: string[], moodsSorted: string[]; export async function getMoods() { if (!moods) @@ -42,7 +57,8 @@ export async function render( session: req.session, flashes: req.flash(), moods, - currentMood + currentMood, + nonce }; res.render(page, { ...o, ...stuff }); } diff --git a/static/css/dashboard.css b/static/css/dashboard.css index 0b930de..4e85d00 100644 --- a/static/css/dashboard.css +++ b/static/css/dashboard.css @@ -9,21 +9,6 @@ form *:last-child { margin-bottom: 0; } -.feed-update { - background-color: #d5d4bb; - height: fit-content; - word-wrap: break-word; -} -.feed-update div:first-child { - display: flex; - justify-content: space-between; -} -.feed-update div:last-child { - text-align: right; - color: #3f3f38; - font-style: italic; -} - #dashboard-update-form * { width: 50%; min-width: 128px; @@ -43,20 +28,51 @@ form *:last-child { justify-content: space-between; } -@media screen and (min-width: 900px) { - #dashboard-feed { - display: flex; - flex-wrap: wrap; - gap: 12px; - } - .feed-update { - padding: 4px; - width: 280px; - } +#journal-update { + max-width: 880px; } -@media screen and (max-width: 900px) { - .feed-update { - border-bottom: 1px solid #afa870; - padding: 8px; - } + +#journal-update .input { + display: flex; + align-items: center; + justify-content: space-between; +} + +#journal-update .input span { + cursor: default; +} + +#ovm { + display: flex; +} +#visibility-control * { + display: inline; + margin: 4px; +} + +input.ovm-input { + opacity: 0; + width: 0; +} +input.ovm-input:first-child { + position: absolute; + width: 1px; +} + +label.ovm-input > img { + border-width: 1px 0 1px 1px; + border-style: solid; + border-color: #afa870; + background-color: #d5d4bb; + cursor: pointer; + padding: 8px; + width: 64px; + aspect-ratio: 1/1; +} +input.ovm-input:checked+label > img { + background-color: #999883; + filter: invert(1); +} +label.ovm-input:last-child > img { + border-right-width: 1px; } diff --git a/static/css/main.css b/static/css/main.css index 92ae046..af0b696 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -169,8 +169,42 @@ select { cursor: pointer; } +.feed-update { + background-color: #d5d4bb; + height: fit-content; + word-wrap: break-word; +} +.feed-update div:first-child { + display: flex; + justify-content: space-between; +} +.feed-update div:last-child { + text-align: right; + color: #3f3f38; + font-style: italic; +} + @font-face { font-family: "Gohufont 14"; src: url("/fonts/gohufont-14.ttf") format("truetype"), url("/fonts/gohufont-14.woff") format("woff"); + font-display: swap; +} + +@media screen and (min-width: 900px) { + #feed { + display: flex; + flex-wrap: wrap; + gap: 12px; + } + .feed-update { + padding: 4px; + width: 280px; + } +} +@media screen and (max-width: 900px) { + .feed-update { + border-bottom: 1px solid #afa870; + padding: 8px; + } } diff --git a/static/img/down.svg b/static/img/down.svg new file mode 100644 index 0000000..fcbcd96 --- /dev/null +++ b/static/img/down.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/downdown.svg b/static/img/downdown.svg new file mode 100644 index 0000000..d909aa9 --- /dev/null +++ b/static/img/downdown.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/line.svg b/static/img/line.svg new file mode 100644 index 0000000..bfcfb7c --- /dev/null +++ b/static/img/line.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/logo.svg b/static/img/logo.svg index 4240d7e..b090ac0 100644 --- a/static/img/logo.svg +++ b/static/img/logo.svg @@ -1 +1,5 @@ - \ No newline at end of file + + + + diff --git a/static/img/up.svg b/static/img/up.svg new file mode 100644 index 0000000..30b71a6 --- /dev/null +++ b/static/img/up.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/upup.svg b/static/img/upup.svg new file mode 100644 index 0000000..c022bee --- /dev/null +++ b/static/img/upup.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/moods.txt b/static/moods.txt index 5679711..d7eac79 100644 --- a/static/moods.txt +++ b/static/moods.txt @@ -1 +1 @@ -abandoned;abnormal;abused;accepted;accomplished;achy;active;addicted;adored;adventurous;affectionate;aggravated;aggressive;agitated;alienated;alive;alluring;alone;aloof;alright;amazed;amazing;ambitious;ambivalent;amorous;amused;angelic;angry;angsty;annoyed;annoying;antisocial;antsy;anxious;apathetic;apologetic;appalled;appreciated;appreciative;apprehensive;argumentative;aroused;artistic;ashamed;asleep;astonished;astounded;athletic;attractive;audacious;awake;awesome;awestruck;awful;awkward;bad;baffled;balanced;bashful;beaming;beat;beautiful;befuddled;belittled;belligerent;bemused;betrayed;better;bewildered;bewitched;bipolar;bitchy;bitter;bittersweet;bizarre;blah;blank;blasphemous;bleak;bleh;blessed;blind;blissful;bloated;blonde;blotto;blue;boastful;boisterous;bold;bonkers;bootylicious;bored;bothered;bouncy;boyish;braindead;bratty;brave;breathless;bright;brilliant;broke;broken;broken-hearted;bruised;bubbly;bummed;burdened;burned;burned-out;businesslike;busy;buzzed;caffeinated;callous;calm;cantankerous;capricious;captivated;carefree;careless;catatonic;catty;cautious;cavalier;celebratory;challenged;changed;chaotic;charitable;charmed;charming;cheated;cheeky;cheerful;cheery;cheesy;cherished;childish;chilled;chillin;chipper;christmasy;classy;claustrophobic;clean;clever;clingy;clueless;clumsy;cocky;cold;colorful;comfortable;comforted;compassionate;competitive;complacent;complete;complicated;concerned;confident;confined;conflicted;confused;confuzzled;congested;connected;constipated;contemplative;content;controlled;cool;copacetic;corny;cosmic;courageous;coy;cozy;crabby;crafty;crampy;cranky;crappy;crazy;creative;creepy;crestfallen;cruel;crummy;crushed;crusty;cuddly;cunning;curious;cursed;cute;cynical;damned;dancy;dandy;dangerous;daring;dark;daunted;dazed;dead;decadent;decaffeinated;deceived;decent;deep;defeated;defensive;defiant;deficient;deflated;dejected;delicious;delighted;delirious;delusional;demented;demonic;demure;dense;depraved;depressed;deprived;deranged;deserted;desolate;desperate;despondent;destroyed;destructive;detached;determined;devastated;devilish;devious;devoted;different;dirty;disappointed;discarded;discombobulated;disconnected;discouraged;diseased;disenchanted;disengaged;disgruntled;disgusted;disillusioned;dismayed;disoriented;disrespected;dissociated;distant;distracted;distraught;distressed;disturbed;ditched;ditzy;divine;dizzy;dodgy;domestic;done;doomed;dorky;doubtful;dour;down;drained;dramatic;dreamy;driven;drowsy;drunk;dry;ducky;dull;dumb;dumbfounded;dysmorphic;dysphoric;eager;eccentric;ecstatic;edgy;eek!;effervescent;eh;elated;electric;electrified;embarrassed;emotional;emotionless;empathetic;empowered;empty;enamored;enchanted;encouraged;energetic;energized;engaged;enigmatic;enlightened;enraged;enraptured;entertained;enthralled;enthusiastic;envious;epic;erotic;esoteric;ethereal;euphoric;evil;exasperated;excellent;excited;excluded;exhausted;existential;exotic;expectant;experimental;explosive;exuberant;fabulous;faded;faithful;fake;famished;fancy;fantastic;fat;fatigued;fed up;feisty;feline;feral;festive;fetching;feverish;fickle;fidgety;fine;finite;fired up;fixated;flabbergasted;flashy;flattered;flighty;flippant;flirty;fluffy;flummoxed;flustered;focused;foggy;foolish;forgetful;forgiving;forgotten;forlorn;forsaken;fortuitous;found;foxy;fragile;frantic;frazzled;freaked;freaky;free;freezing;fresh;friendly;friendzoned;frightened;frisky;frozen;fruity;frumpy;frustrated;fulfilled;full;fun;funky;funny;furious;fuzzy;gay;geeked;geeky;gelatinous;generous;genki;ghetto;giddy;giggly;girly;glad;glamorous;gleeful;glittery;gloomy;glorious;glowing;glum;good;goofy;gorgeous;gothic;grand;grateful;great;greedy;groggy;groovy;gross;grouchy;grounded;grr;grumpy;guilty;hangry;hanukkahy;happy;hardcore;hated;hateful;haunted;headachy;healthy;heartbroken;heavenly;hella good;helpful;helpless;heroic;hesitant;high;hip;historical;hollow;holy;homesick;hopeful;hopeless;hormonal;horny;horrible;horrified;hostile;hot;hotheaded;humbled;humiliated;hungover;hungry;hurt;hyggelig;hyper;hyperactive;hypocritical;hysterical;icky;idiotic;ignorant;ignored;ill;illuminated;imaginative;immature;impatient;impish;important;impressed;in denial;in love;in pain;inadequate;incomplete;incredible;incredulous;indecisive;independent;indifferent;indulgent;industrious;infatuated;inferior;infinite;infuriated;innocent;inquisitive;insane;insatiable;insecure;insightful;insignificant;inspired;insulted;intellectual;intelligent;interested;intimidated;intoxicated;intrepid;intrigued;introspective;inventive;invincible;invisible;irate;irked;irreverent;irritable;irritated;isolated;itchy;jaded;jazzed;jealous;jetlagged;jiggy;jinxed;jittery;jocund;jolly;jovial;joyful;jubilant;jumbled;jumpy;kawaii;keen;kinky;klutzy;knackered;knowledgeable;kooky;lackadaisical;lame;lazy;leery;left out;lethargic;liberated;lifeless;listless;livid;lonely;longing;loopy;lost;loud;lousy;lovable;loved;lovely;lovesick;lovestruck;loving;loyal;lucky;lustful;mad;magical;malicious;manic;manipulative;manly;marvelous;masochistic;mature;mean;medicated;mediocre;megalomaniacal;meh;melancholy;mellow;melodramatic;mercurial;merry;messy;mid;miffed;misanthropic;mischievous;miserable;misplaced;misunderstood;mixed;moodless;moody;mopey;morbid;morose;mortified;motivated;mushy;musical;mysterious;mystic;mystified;naive;naked;narcissistic;nasty;natural;naughty;nauseous;needy;neglected;nerdy;nervous;neurotic;neutral;nice;nifty;nonchalant;normal;nostalgic;nothing;numb;nurturing;nutty;oblivious;obnoxious;obscene;obsessed;odd;offended;ok;old;olympic;optimistic;organized;orgasmic;ornery;outgoing;outraged;overheated;overjoyed;overloaded;overreactive;overstimulated;overwhelmed;overworked;oy;pained;pampered;panicked;paranoid;passionate;passive;pathetic;patient;patriotic;peaceful;peachy;peeved;pensive;peppy;perfect;perky;perplexed;perturbed;perverted;pessimistic;petrified;petty;philosophical;pi;pink;pissed;pissed off;pissy;placid;playful;pleasant;pleased;pmsy;poetic;pooped;popular;positive;pouty;powerful;powerless;precious;predatory;pregnant;preppy;pressured;pretty;productive;protective;proud;provocative;psyched;psychedelic;psychic;psycho;psychotic;pumped;punchy;punk;punky;pure;puzzled;queasy;queer;quiet;quirky;quixotic;rad;radiant;rambunctious;random;randy;raunchy;ready;rebellious;reborn;recalcitrant;reclusive;reflective;refreshed;regal;regretful;rejected;rejuvenated;relaxed;relieved;religious;reluctant;reminiscent;renewed;repulsed;resentful;reserved;resigned;resilient;resolute;resourceful;responsible;rested;restless;retro;rich;ridiculed;righteous;rofl;romantic;rowdy;royal;rude;rushed;sad;sadistic;safe;salty;sane;sapphic;sappy;sarcastic;sardonic;sassy;sated;satisfied;saturnine;saucy;scandalous;scared;scattered;schizophrenic;scholarly;scorned;screwed;secretive;secure;sedated;seductive;self-conscious;selfish;sensitive;sensual;sentimental;serene;serious;sexy;shady;shaken;shallow;shattered;sheepish;shifty;shiny;shocked;shy;sick;silly;sinful;single;sinister;skeptical;sketchy;slaphappy;sleazy;sleepless;sleepy;slinky;slothful;sluggish;slutty;sly;smart;smashing;smelly;smiley;smitten;smooth;smug;snarky;snazzy;sneaky;sneezy;sniffly;so-so;sober;social;somber;sophisticated;sore;sorrowful;sorry;sour;spacey;sparkly;spastic;spazzy;special;spectacular;speechless;spent;spicy;spiffy;spirited;spiritual;spiteful;splendid;split;spoiled;spontaneous;spooky;sporty;spunky;squishy;stabby;stable;starstruck;starving;stellar;sticky;stimulated;stoic;stoked;stoned;stormy;strange;stressed;strong;stubborn;stuck;studious;stuffed;stuffy;stumped;stunned;stunning;stupid;stylish;subdued;sublime;submissive;successful;sullen;sunny;super;superb;superior;supine;surly;surprised;surreal;suspicious;swamped;swanky;sweaty;sweet;swell;sympathetic;taciturn;talented;talkative;tearful;tenacious;tense;terrible;terrified;thankful;thirsty;thoughtful;thrilled;tickled;tickled pink;tipsy;tired;tormented;torn;tortured;touchy;toxic;tragic;tranquil;trapped;tricky;trippy;triumphant;troubled;twisted;twitchy;twitterpated;ugh;ugly;unappreciated;unattractive;uncertain;uncomfortable;undecided;understimulated;undesirable;uneasy;unfulfilled;ungrateful;ungrounded;unhappy;unhealthy;unimportant;uninspired;unique;unknown;unloved;unlucky;unmedicated;unmotivated;unpleasant;unproductive;unreal;unsafe;unsatisfied;unsettled;unstable;unsteady;unstoppable;unsure;unwanted;unworthy;upbeat;uplifted;upset;upside-down;used;useful;useless;vacant;vaccinated;vain;vamped;vengeful;vexed;vibrant;vicious;victorious;vindictive;violated;violent;virtuous;volatile;vulnerable;wacky;wanted;warm;wasted;weak;weary;weepy;weird;well;wet;whatever;whimsical;whiney;whiny;wicked;wild;wired;wise;wishful;wistful;witchy;withdrawn;witty;wonderful;woozy;worn;worried;worthless;wounded;wretched;wrong;xenophilic;young;yucky;yummy;zany;zapped;zealous;zen;zesty;zoned;zonked;afraid;fearful;on edge;subhuman;stagnant;mesmerized;erratic;uxorious;cybernetic;contagious;rough;tough;wrecked;otherworldly;loveless;disgusting;emo;defiled;mournful;autumnal;familial;unaccomplished;hyped;undervalued;ignited;locked in;sore;flamboyant;sigh;estranged;employed;bloody;oh my god;schlumped;revolutionary;spumoni;solemn;synthetic;ectoplasmic;inconsolable;ruined;bedraggled;procrastinatory;hurricaned;depleted;burdensome;undead;reanimated;validated;invalidated;cloudy;clouded;eepy;noticed;perceived;seen;interesting \ No newline at end of file +abandoned;abnormal;abused;accepted;accomplished;achy;active;addicted;adored;adventurous;affectionate;aggravated;aggressive;agitated;alienated;alive;alluring;alone;aloof;alright;amazed;amazing;ambitious;ambivalent;amorous;amused;angelic;angry;angsty;annoyed;annoying;antisocial;antsy;anxious;apathetic;apologetic;appalled;appreciated;appreciative;apprehensive;argumentative;aroused;artistic;ashamed;asleep;astonished;astounded;athletic;attractive;audacious;awake;awesome;awestruck;awful;awkward;bad;baffled;balanced;bashful;beaming;beat;beautiful;befuddled;belittled;belligerent;bemused;betrayed;better;bewildered;bewitched;bipolar;bitchy;bitter;bittersweet;bizarre;blah;blank;blasphemous;bleak;bleh;blessed;blind;blissful;bloated;blonde;blotto;blue;boastful;boisterous;bold;bonkers;bootylicious;bored;bothered;bouncy;boyish;braindead;bratty;brave;breathless;bright;brilliant;broke;broken;broken-hearted;bruised;bubbly;bummed;burdened;burned;burned-out;businesslike;busy;buzzed;caffeinated;callous;calm;cantankerous;capricious;captivated;carefree;careless;catatonic;catty;cautious;cavalier;celebratory;challenged;changed;chaotic;charitable;charmed;charming;cheated;cheeky;cheerful;cheery;cheesy;cherished;childish;chilled;chillin;chipper;christmasy;classy;claustrophobic;clean;clever;clingy;clueless;clumsy;cocky;cold;colorful;comfortable;comforted;compassionate;competitive;complacent;complete;complicated;concerned;confident;confined;conflicted;confused;confuzzled;congested;connected;constipated;contemplative;content;controlled;cool;copacetic;corny;cosmic;courageous;coy;cozy;crabby;crafty;crampy;cranky;crappy;crazy;creative;creepy;crestfallen;cruel;crummy;crushed;crusty;cuddly;cunning;curious;cursed;cute;cynical;damned;dancy;dandy;dangerous;daring;dark;daunted;dazed;dead;decadent;decaffeinated;deceived;decent;deep;defeated;defensive;defiant;deficient;deflated;dejected;delicious;delighted;delirious;delusional;demented;demonic;demure;dense;depraved;depressed;deprived;deranged;deserted;desolate;desperate;despondent;destroyed;destructive;detached;determined;devastated;devilish;devious;devoted;different;dirty;disappointed;discarded;discombobulated;disconnected;discouraged;diseased;disenchanted;disengaged;disgruntled;disgusted;disillusioned;dismayed;disoriented;disrespected;dissociated;distant;distracted;distraught;distressed;disturbed;ditched;ditzy;divine;dizzy;dodgy;domestic;done;doomed;dorky;doubtful;dour;down;drained;dramatic;dreamy;driven;drowsy;drunk;dry;ducky;dull;dumb;dumbfounded;dysmorphic;dysphoric;eager;eccentric;ecstatic;edgy;eek!;effervescent;eh;elated;electric;electrified;embarrassed;emotional;emotionless;empathetic;empowered;empty;enamored;enchanted;encouraged;energetic;energized;engaged;enigmatic;enlightened;enraged;enraptured;entertained;enthralled;enthusiastic;envious;epic;erotic;esoteric;ethereal;euphoric;evil;exasperated;excellent;excited;excluded;exhausted;existential;exotic;expectant;experimental;explosive;exuberant;fabulous;faded;faithful;fake;famished;fancy;fantastic;fat;fatigued;fed up;feisty;feline;feral;festive;fetching;feverish;fickle;fidgety;fine;finite;fired up;fixated;flabbergasted;flashy;flattered;flighty;flippant;flirty;fluffy;flummoxed;flustered;focused;foggy;foolish;forgetful;forgiving;forgotten;forlorn;forsaken;fortuitous;found;foxy;fragile;frantic;frazzled;freaked;freaky;free;freezing;fresh;friendly;friendzoned;frightened;frisky;frozen;fruity;frumpy;frustrated;fulfilled;full;fun;funky;funny;furious;fuzzy;gay;geeked;geeky;gelatinous;generous;genki;ghetto;giddy;giggly;girly;glad;glamorous;gleeful;glittery;gloomy;glorious;glowing;glum;good;goofy;gorgeous;gothic;grand;grateful;great;greedy;groggy;groovy;gross;grouchy;grounded;grr;grumpy;guilty;hangry;hanukkahy;happy;hardcore;hated;hateful;haunted;headachy;healthy;heartbroken;heavenly;hella good;helpful;helpless;heroic;hesitant;high;hip;historical;hollow;holy;homesick;hopeful;hopeless;hormonal;horny;horrible;horrified;hostile;hot;hotheaded;humbled;humiliated;hungover;hungry;hurt;hyggelig;hyper;hyperactive;hypocritical;hysterical;icky;idiotic;ignorant;ignored;ill;illuminated;imaginative;immature;impatient;impish;important;impressed;in denial;in love;in pain;inadequate;incomplete;incredible;incredulous;indecisive;independent;indifferent;indulgent;industrious;infatuated;inferior;infinite;infuriated;innocent;inquisitive;insane;insatiable;insecure;insightful;insignificant;inspired;insulted;intellectual;intelligent;interested;intimidated;intoxicated;intrepid;intrigued;introspective;inventive;invincible;invisible;irate;irked;irreverent;irritable;irritated;isolated;itchy;jaded;jazzed;jealous;jetlagged;jiggy;jinxed;jittery;jocund;jolly;jovial;joyful;jubilant;jumbled;jumpy;kawaii;keen;kinky;klutzy;knackered;knowledgeable;kooky;lackadaisical;lame;lazy;leery;left out;lethargic;liberated;lifeless;listless;livid;lonely;longing;loopy;lost;loud;lousy;lovable;loved;lovely;lovesick;lovestruck;loving;loyal;lucky;lustful;mad;magical;malicious;manic;manipulative;manly;marvelous;masochistic;mature;mean;medicated;mediocre;megalomaniacal;meh;melancholy;mellow;melodramatic;mercurial;merry;messy;mid;miffed;misanthropic;mischievous;miserable;misplaced;misunderstood;mixed;moodless;moody;mopey;morbid;morose;mortified;motivated;mushy;musical;mysterious;mystic;mystified;naive;naked;narcissistic;nasty;natural;naughty;nauseous;needy;neglected;nerdy;nervous;neurotic;neutral;nice;nifty;nonchalant;normal;nostalgic;nothing;numb;nurturing;nutty;oblivious;obnoxious;obscene;obsessed;odd;offended;ok;old;olympic;optimistic;organized;orgasmic;ornery;outgoing;outraged;overheated;overjoyed;overloaded;overreactive;overstimulated;overwhelmed;overworked;oy;pained;pampered;panicked;paranoid;passionate;passive;pathetic;patient;patriotic;peaceful;peachy;peeved;pensive;peppy;perfect;perky;perplexed;perturbed;perverted;pessimistic;petrified;petty;philosophical;pi;pink;pissed;pissed off;pissy;placid;playful;pleasant;pleased;pmsy;poetic;pooped;popular;positive;pouty;powerful;powerless;precious;predatory;pregnant;preppy;pressured;pretty;productive;protective;proud;provocative;psyched;psychedelic;psychic;psycho;psychotic;pumped;punchy;punk;punky;pure;puzzled;queasy;queer;quiet;quirky;quixotic;rad;radiant;rambunctious;random;randy;raunchy;ready;rebellious;reborn;recalcitrant;reclusive;reflective;refreshed;regal;regretful;rejected;rejuvenated;relaxed;relieved;religious;reluctant;reminiscent;renewed;repulsed;resentful;reserved;resigned;resilient;resolute;resourceful;responsible;rested;restless;retro;rich;ridiculed;righteous;rofl;romantic;rowdy;royal;rude;rushed;sad;sadistic;safe;salty;sane;sapphic;sappy;sarcastic;sardonic;sassy;sated;satisfied;saturnine;saucy;scandalous;scared;scattered;schizophrenic;scholarly;scorned;screwed;secretive;secure;sedated;seductive;self-conscious;selfish;sensitive;sensual;sentimental;serene;serious;sexy;shady;shaken;shallow;shattered;sheepish;shifty;shiny;shocked;shy;sick;silly;sinful;single;sinister;skeptical;sketchy;slaphappy;sleazy;sleepless;sleepy;slinky;slothful;sluggish;slutty;sly;smart;smashing;smelly;smiley;smitten;smooth;smug;snarky;snazzy;sneaky;sneezy;sniffly;so-so;sober;social;somber;sophisticated;sore;sorrowful;sorry;sour;spacey;sparkly;spastic;spazzy;special;spectacular;speechless;spent;spicy;spiffy;spirited;spiritual;spiteful;splendid;split;spoiled;spontaneous;spooky;sporty;spunky;squishy;stabby;stable;starstruck;starving;stellar;sticky;stimulated;stoic;stoked;stoned;stormy;strange;stressed;strong;stubborn;stuck;studious;stuffed;stuffy;stumped;stunned;stunning;stupid;stylish;subdued;sublime;submissive;successful;sullen;sunny;super;superb;superior;supine;surly;surprised;surreal;suspicious;swamped;swanky;sweaty;sweet;swell;sympathetic;taciturn;talented;talkative;tearful;tenacious;tense;terrible;terrified;thankful;thirsty;thoughtful;thrilled;tickled;tickled pink;tipsy;tired;tormented;torn;tortured;touchy;toxic;tragic;tranquil;trapped;tricky;trippy;triumphant;troubled;twisted;twitchy;twitterpated;ugh;ugly;unappreciated;unattractive;uncertain;uncomfortable;undecided;understimulated;undesirable;uneasy;unfulfilled;ungrateful;ungrounded;unhappy;unhealthy;unimportant;uninspired;unique;unknown;unloved;unlucky;unmedicated;unmotivated;unpleasant;unproductive;unreal;unsafe;unsatisfied;unsettled;unstable;unsteady;unstoppable;unsure;unwanted;unworthy;upbeat;uplifted;upset;upside-down;used;useful;useless;vacant;vaccinated;vain;vamped;vengeful;vexed;vibrant;vicious;victorious;vindictive;violated;violent;virtuous;volatile;vulnerable;wacky;wanted;warm;wasted;weak;weary;weepy;weird;well;wet;whatever;whimsical;whiney;whiny;wicked;wild;wired;wise;wishful;wistful;witchy;withdrawn;witty;wonderful;woozy;worn;worried;worthless;wounded;wretched;wrong;xenophilic;young;yucky;yummy;zany;zapped;zealous;zen;zesty;zoned;zonked;afraid;fearful;on edge;subhuman;stagnant;mesmerized;erratic;uxorious;cybernetic;contagious;rough;tough;wrecked;otherworldly;loveless;disgusting;emo;defiled;mournful;autumnal;familial;unaccomplished;hyped;undervalued;ignited;locked in;sore;flamboyant;sigh;estranged;employed;bloody;oh my god;schlumped;revolutionary;spumoni;solemn;synthetic;ectoplasmic;inconsolable;ruined;bedraggled;procrastinatory;depleted;burdensome;undead;reanimated;validated;invalidated;cloudy;clouded;eepy;noticed;perceived;seen;interesting;adoring;conscious;willing \ No newline at end of file diff --git a/views/_feed.pug b/views/_feed.pug new file mode 100644 index 0000000..6ee981e --- /dev/null +++ b/views/_feed.pug @@ -0,0 +1,14 @@ +mixin feed(feed, hideUser) + if feed.length > 0 + #feed + for update of feed + .feed-update + div + a(href=`/users/${update.user}`) + if !hideUser + | #{update.user} + strong= update.mood + div= update.desc || "[no mood description provided]" + div= update.date + else + span [no updates] diff --git a/views/dashboard.pug b/views/dashboard.pug index d0f70c9..a321dfa 100644 --- a/views/dashboard.pug +++ b/views/dashboard.pug @@ -25,8 +25,8 @@ block content details(style="margin-bottom:1em;") summary Moderation p If you're seeing this, you are part of the exclusive club of moderators! You are now entitled to pizza for the rest of your life! And the ability to ban people and create new moods! For FREE! - form#dashboard-update-form(action="/update", method="post", onsubmit="disable(this);") - select(name="mood") + form#dashboard-update-form(action="/update/mood", method="post", onsubmit="disable(this);") + select(name="mood", required) //- Maybe put the index of the mood in the value of the option element //- so that indexOf (slow) wont have to be used everytime mood is updated for mood of moodsSorted @@ -34,17 +34,8 @@ block content textarea(name="desc", placeholder="mood description (max 512 chars)", rows="5", maxlength="512") button(type="submit") Update h1(style="margin-top:0.5em;") Feed - if recentUpdates.length > 0 - #dashboard-feed - for update of recentUpdates - .feed-update - div - a(href=`/users/${update.user}`)= update.user - strong= update.mood - div= update.desc || "[no mood description provided]" - div= update.date - else - span [no updates] + include _feed.pug + +feed(recentUpdates) script. function disable(form) { const btn = form.querySelector("button"); diff --git a/views/index.pug b/views/index.pug index 856a0b8..557623d 100644 --- a/views/index.pug +++ b/views/index.pug @@ -15,6 +15,12 @@ block page | a(href="/dashboard") the dashboard | ! + if session.loggedIn + p + | You can record your daily thoughts and feelings, public or private, at your + | + a(href="/journal/") personal journal + | . p strong #{users} | @@ -28,3 +34,7 @@ block page block content p | Hi, this is MiPilin! It lets you tell your friends how you're feeling, as well as keep a journal of your current mood and their trends over time! Due respect goes to imood, from which I borrowed many ideas and basically all of the moods. + h1 Global Feed + p Look at how all these people are doing!!! + include _feed.pug + +feed(feedUpdates) diff --git a/views/journal.pug b/views/journal.pug new file mode 100644 index 0000000..ab29489 --- /dev/null +++ b/views/journal.pug @@ -0,0 +1,48 @@ +extends site.pug + +block head + link(rel="stylesheet", href="/css/dashboard.css") + script(type="module", src="https://cdn.jsdelivr.net/npm/chart.js@4.4.6/dist/chart.umd.min.js", nonce=nonce) + +block content + h1 Your Journal + p This is where you can log your overall mood every day, and get a glimpse at how your life is going so far! + form#journal-update(action="/update/journal", method="post") + .input + span Overall mood change (how do you feel compared to yesterday?) + #ovm + input.ovm-input(type="radio", name="moodDelta", id="moodDelta-mb", value="2", required) + label.ovm-input(for="moodDelta-mb", title="Much better") + img(src="/img/upup.svg", alt="Much better") + + input.ovm-input(type="radio", name="moodDelta", id="moodDelta-b", value="1", required) + label.ovm-input(for="moodDelta-b", title="Better") + img(src="/img/up.svg", alt="Better") + + input.ovm-input(type="radio", name="moodDelta", id="moodDelta-nc", value="0", required checked) + label.ovm-input(for="moodDelta-nc", title="About the same") + img(src="/img/line.svg", alt="About the same") + + input.ovm-input(type="radio", name="moodDelta", id="moodDelta-w", value="-1", required) + label.ovm-input(for="moodDelta-w", title="Worse") + img(src="/img/down.svg", alt="Worse") + + input.ovm-input(type="radio", name="moodDelta", id="moodDelta-mw", value="-2", required) + label.ovm-input(for="moodDelta-mw", title="Much worse") + img(src="/img/downdown.svg", alt="Much worse") + .input + label(for="description") Journal entry for today + textarea(name="description", id="description", placeholder="max 4096 chars", maxlength="4096", cols="60", rows="12") + .input + span Visibility + div#visibility-control + input(type="radio", name="visibility", id="visibility-public", value="1", checked) + label(for="visibility-public") Public + br + input(type="radio", name="visibility", id="visibility-private", value="0") + label(for="visibility-private") Private + br + input(type="radio", name="visibility", id="visibility-moodChange-only", value="2") + label(for="visibility-moodChange-only") Mood only + + button(type="submit") Submit diff --git a/views/site.pug b/views/site.pug index d0c0df9..82fa2ef 100644 --- a/views/site.pug +++ b/views/site.pug @@ -3,6 +3,7 @@ html(lang="en") head meta(charset="UTF-8") meta(name="viewport", content="width=device-width, initial-scale=1.0") + meta(name="description", content="Show your mood to the world!") link(rel="stylesheet", href="/css/main.css") block head title #{title} > mipilin @@ -14,10 +15,9 @@ html(lang="en") nav if session.loggedIn a(href="/dashboard/") Dashboard - a(href="/feed/") Feed + a(href="/journal/") Journal a(href="/logout/") Log out else - a(href="/feed/") Feed a(href="/register/") Sign up a(href="/login") Log in #ticker @@ -41,6 +41,6 @@ html(lang="en") #flashes for [type, flashGroup] of Object.entries(flashes) for flash of flashGroup - div(class=`flash-${type}`)= flash + div(class=`flash-${type}`)!= flash block content diff --git a/views/user.pug b/views/user.pug index 25f6272..e515e48 100644 --- a/views/user.pug +++ b/views/user.pug @@ -27,4 +27,7 @@ block content div(style="margin-left:2ch;word-wrap:break-word;")= userMood.desc || "[no mood description]" else span User has not yet set a mood! - + br + h2 Recent updates + include _feed.pug + +feed(userMoodFeed, true)