From e5412ebcbb65b1cadd2604cdd687b3486ec5a024 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:20:31 +0000 Subject: [PATCH 001/155] chore(deps): bump pino-pretty from 13.1.2 to 13.1.3 Bumps [pino-pretty](https://github.com/pinojs/pino-pretty) from 13.1.2 to 13.1.3. - [Release notes](https://github.com/pinojs/pino-pretty/releases) - [Commits](https://github.com/pinojs/pino-pretty/compare/v13.1.2...v13.1.3) --- updated-dependencies: - dependency-name: pino-pretty dependency-version: 13.1.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/juxtaposition-ui/package.json | 2 +- apps/miiverse-api/package.json | 2 +- package-lock.json | 30 ++++++++++++++++++++---------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 986da831..5f840c19 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -44,7 +44,7 @@ "pako": "^2.1.0", "pino": "^10.1.0", "pino-http": "^11.0.0", - "pino-pretty": "^13.1.2", + "pino-pretty": "^13.1.3", "pjax": "^0.2.8", "prom-client": "^15.1.3", "react": "^19.1.1", diff --git a/apps/miiverse-api/package.json b/apps/miiverse-api/package.json index f371872d..1e49621c 100644 --- a/apps/miiverse-api/package.json +++ b/apps/miiverse-api/package.json @@ -34,7 +34,7 @@ "pako": "^2.1.0", "pino": "^10.1.0", "pino-http": "^11.0.0", - "pino-pretty": "^13.1.2", + "pino-pretty": "^13.1.3", "prom-client": "^15.1.3", "supertest": "^7.1.4", "xmlbuilder": "^15.1.1", diff --git a/package-lock.json b/package-lock.json index 46ef4ca4..f33ab08a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "pako": "^2.1.0", "pino": "^10.1.0", "pino-http": "^11.0.0", - "pino-pretty": "^13.1.2", + "pino-pretty": "^13.1.3", "pjax": "^0.2.8", "prom-client": "^15.1.3", "react": "^19.1.1", @@ -98,7 +98,7 @@ "pako": "^2.1.0", "pino": "^10.1.0", "pino-http": "^11.0.0", - "pino-pretty": "^13.1.2", + "pino-pretty": "^13.1.3", "prom-client": "^15.1.3", "supertest": "^7.1.4", "xmlbuilder": "^15.1.1", @@ -6829,9 +6829,10 @@ ] }, "node_modules/fast-copy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz", + "integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==", + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -9904,20 +9905,20 @@ } }, "node_modules/pino-pretty": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.2.tgz", - "integrity": "sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==", + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", "license": "MIT", "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", - "fast-copy": "^3.0.2", + "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", + "pino-abstract-transport": "^3.0.0", "pump": "^3.0.0", "secure-json-parse": "^4.0.0", "sonic-boom": "^4.0.1", @@ -9927,6 +9928,15 @@ "pino-pretty": "bin.js" } }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, "node_modules/pino-pretty/node_modules/strip-json-comments": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", From 69ff7affbc553686fb8c8b6ed72c8b37089db439 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 4 Jan 2026 12:16:06 +0100 Subject: [PATCH 002/155] feat: port discovery middleware and endpoints to typescript --- .../middleware/{discovery.js => discovery.ts} | 5 +-- apps/juxtaposition-ui/src/models/endpoint.js | 18 ---------- apps/juxtaposition-ui/src/models/endpoint.ts | 34 +++++++++++++++++++ 3 files changed, 37 insertions(+), 20 deletions(-) rename apps/juxtaposition-ui/src/middleware/{discovery.js => discovery.ts} (88%) delete mode 100644 apps/juxtaposition-ui/src/models/endpoint.js create mode 100644 apps/juxtaposition-ui/src/models/endpoint.ts diff --git a/apps/juxtaposition-ui/src/middleware/discovery.js b/apps/juxtaposition-ui/src/middleware/discovery.ts similarity index 88% rename from apps/juxtaposition-ui/src/middleware/discovery.js rename to apps/juxtaposition-ui/src/middleware/discovery.ts index b4bbacc3..04a0b578 100644 --- a/apps/juxtaposition-ui/src/middleware/discovery.js +++ b/apps/juxtaposition-ui/src/middleware/discovery.ts @@ -1,7 +1,8 @@ import { database as db } from '@/database'; import { config } from '@/config'; +import type { RequestHandler } from 'express'; -export async function checkDiscovery(request, response, next) { +export const checkDiscovery: RequestHandler = async (request, response, next) => { const discovery = await db.getEndPoint(config.serverEnvironment); if (!discovery || discovery.status !== 0) { @@ -33,4 +34,4 @@ export async function checkDiscovery(request, response, next) { } next(); -} +}; diff --git a/apps/juxtaposition-ui/src/models/endpoint.js b/apps/juxtaposition-ui/src/models/endpoint.js deleted file mode 100644 index 6512223c..00000000 --- a/apps/juxtaposition-ui/src/models/endpoint.js +++ /dev/null @@ -1,18 +0,0 @@ -import { Schema, model } from 'mongoose'; - -export const endpointSchema = new Schema({ - status: Number, - server_access_level: String, - topics: Boolean, - guest_access: Boolean, - new_users: { - type: Boolean, - default: true - }, - host: String, - api_host: String, - portal_host: String, - n3ds_host: String -}); - -export const ENDPOINT = model('ENDPOINT', endpointSchema); diff --git a/apps/juxtaposition-ui/src/models/endpoint.ts b/apps/juxtaposition-ui/src/models/endpoint.ts new file mode 100644 index 00000000..aa0a4744 --- /dev/null +++ b/apps/juxtaposition-ui/src/models/endpoint.ts @@ -0,0 +1,34 @@ +import { Schema, model } from 'mongoose'; +import type { HydratedDocument } from 'mongoose'; + +export type Endpoint = { + status: number; + server_access_level: string; + topics: boolean; + guest_access: boolean; + new_users: boolean; + host: string; + api_host: string; + portal_host: string; + n3ds_host: string; +} & Document; + +export type HydratedEndpointDocument = HydratedDocument; + +export const endpointSchema = new Schema({ + status: Number, + server_access_level: String, + topics: Boolean, + guest_access: Boolean, + new_users: { + type: Boolean, + default: true + }, + host: String, + api_host: String, + portal_host: String, + n3ds_host: String +}); + +export const Endpoint = model('ENDPOINT', endpointSchema); +export const ENDPOINT = Endpoint; From 616f211d8efc76ce9bdcef2ce340547d253163e0 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 4 Jan 2026 12:19:48 +0100 Subject: [PATCH 003/155] feat: port detectVersion middleware to typescript --- .../src/middleware/{detectVersion.js => detectVersion.ts} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename apps/juxtaposition-ui/src/middleware/{detectVersion.js => detectVersion.ts} (70%) diff --git a/apps/juxtaposition-ui/src/middleware/detectVersion.js b/apps/juxtaposition-ui/src/middleware/detectVersion.ts similarity index 70% rename from apps/juxtaposition-ui/src/middleware/detectVersion.js rename to apps/juxtaposition-ui/src/middleware/detectVersion.ts index 5219a352..7dbfb139 100644 --- a/apps/juxtaposition-ui/src/middleware/detectVersion.js +++ b/apps/juxtaposition-ui/src/middleware/detectVersion.ts @@ -1,7 +1,7 @@ import { processLanguage } from '@/util'; +import type { Request, RequestHandler } from 'express'; -export async function detectVersion(request, response, next) { - request.timerDate = Date.now(); +export const detectVersion: RequestHandler = async (request, response, next) => { // Check the domain and set the directory if (includes(request, 'juxt')) { request.directory = 'web'; @@ -13,8 +13,8 @@ export async function detectVersion(request, response, next) { request.isWrite = request.method === 'POST' || request.method === 'PUT' || request.method === 'DELETE'; next(); -} +}; -function includes(request, domain) { +function includes(request: Request, domain: string): boolean { return request.subdomains.findIndex(element => element.includes(domain)) !== -1; } From 9277f3952358bbdda7f0b2a266cf266fbc93794d Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 4 Jan 2026 12:25:42 +0100 Subject: [PATCH 004/155] chore: unify endpoint model for miiverse-api and juxtaposition-ui --- apps/juxtaposition-ui/src/models/endpoint.ts | 2 +- apps/miiverse-api/src/database.ts | 2 +- apps/miiverse-api/src/models/endpoint.ts | 25 ++++++++++++++++--- .../services/discovery/routes/discovery.ts | 2 +- .../src/types/mongoose/endpoint.ts | 16 ------------ 5 files changed, 25 insertions(+), 22 deletions(-) delete mode 100644 apps/miiverse-api/src/types/mongoose/endpoint.ts diff --git a/apps/juxtaposition-ui/src/models/endpoint.ts b/apps/juxtaposition-ui/src/models/endpoint.ts index aa0a4744..13ebdf9d 100644 --- a/apps/juxtaposition-ui/src/models/endpoint.ts +++ b/apps/juxtaposition-ui/src/models/endpoint.ts @@ -30,5 +30,5 @@ export const endpointSchema = new Schema({ n3ds_host: String }); -export const Endpoint = model('ENDPOINT', endpointSchema); +export const Endpoint = model('Endpoint', endpointSchema); export const ENDPOINT = Endpoint; diff --git a/apps/miiverse-api/src/database.ts b/apps/miiverse-api/src/database.ts index e4a0e297..f3d1b4b2 100644 --- a/apps/miiverse-api/src/database.ts +++ b/apps/miiverse-api/src/database.ts @@ -7,10 +7,10 @@ import { Endpoint } from '@/models/endpoint'; import { Post } from '@/models/post'; import { Settings } from '@/models/settings'; import { config } from '@/config'; +import type { HydratedEndpointDocument } from '@/models/endpoint'; import type { HydratedConversationDocument } from '@/models/conversation'; import type { HydratedContentDocument } from '@/types/mongoose/content'; import type { HydratedSettingsDocument } from '@/types/mongoose/settings'; -import type { HydratedEndpointDocument } from '@/types/mongoose/endpoint'; import type { HydratedPostDocument, IPostInput } from '@/types/mongoose/post'; import type { HydratedCommunityDocument } from '@/types/mongoose/community'; diff --git a/apps/miiverse-api/src/models/endpoint.ts b/apps/miiverse-api/src/models/endpoint.ts index 19c3b2ab..13ebdf9d 100644 --- a/apps/miiverse-api/src/models/endpoint.ts +++ b/apps/miiverse-api/src/models/endpoint.ts @@ -1,15 +1,34 @@ import { Schema, model } from 'mongoose'; -import type { IEndpoint, EndpointModel } from '@/types/mongoose/endpoint'; +import type { HydratedDocument } from 'mongoose'; -const endpointSchema = new Schema({ +export type Endpoint = { + status: number; + server_access_level: string; + topics: boolean; + guest_access: boolean; + new_users: boolean; + host: string; + api_host: string; + portal_host: string; + n3ds_host: string; +} & Document; + +export type HydratedEndpointDocument = HydratedDocument; + +export const endpointSchema = new Schema({ status: Number, server_access_level: String, topics: Boolean, guest_access: Boolean, + new_users: { + type: Boolean, + default: true + }, host: String, api_host: String, portal_host: String, n3ds_host: String }); -export const Endpoint = model('Endpoint', endpointSchema); +export const Endpoint = model('Endpoint', endpointSchema); +export const ENDPOINT = Endpoint; diff --git a/apps/miiverse-api/src/services/discovery/routes/discovery.ts b/apps/miiverse-api/src/services/discovery/routes/discovery.ts index 231c1f55..71a6c51d 100644 --- a/apps/miiverse-api/src/services/discovery/routes/discovery.ts +++ b/apps/miiverse-api/src/services/discovery/routes/discovery.ts @@ -2,7 +2,7 @@ import express from 'express'; import xmlbuilder from 'xmlbuilder'; import { getEndpoint } from '@/database'; import { ApiErrorCode, badRequest, serverError } from '@/errors'; -import type { HydratedEndpointDocument } from '@/types/mongoose/endpoint'; +import type { HydratedEndpointDocument } from '@/models/endpoint'; const router = express.Router(); diff --git a/apps/miiverse-api/src/types/mongoose/endpoint.ts b/apps/miiverse-api/src/types/mongoose/endpoint.ts deleted file mode 100644 index 9738a136..00000000 --- a/apps/miiverse-api/src/types/mongoose/endpoint.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Model, HydratedDocument } from 'mongoose'; - -export interface IEndpoint { - status: number; - server_access_level: string; - topics: boolean; - guest_access: boolean; - host: string; - api_host: string; - portal_host: string; - n3ds_host: string; -} - -export type EndpointModel = Model; - -export type HydratedEndpointDocument = HydratedDocument; From 49a1e9005219dc97d0ee98b8773c8ed1448372ca Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 4 Jan 2026 13:06:21 +0100 Subject: [PATCH 005/155] feat: port reports to typescript --- apps/juxtaposition-ui/src/models/report.js | 30 -------- apps/juxtaposition-ui/src/models/report.ts | 68 +++++++++++++++++++ apps/miiverse-api/src/models/report.ts | 35 ++++++++-- .../miiverse-api/src/types/mongoose/report.ts | 8 ++- 4 files changed, 105 insertions(+), 36 deletions(-) delete mode 100644 apps/juxtaposition-ui/src/models/report.js create mode 100644 apps/juxtaposition-ui/src/models/report.ts diff --git a/apps/juxtaposition-ui/src/models/report.js b/apps/juxtaposition-ui/src/models/report.js deleted file mode 100644 index 31702486..00000000 --- a/apps/juxtaposition-ui/src/models/report.js +++ /dev/null @@ -1,30 +0,0 @@ -import { Schema, model } from 'mongoose'; - -export const ReportSchema = new Schema({ - pid: Number, - reported_by: Number, - post_id: String, - reason: Number, - message: String, - created_at: { - type: Date, - default: new Date() - }, - resolved: { - type: Boolean, - default: false - }, - note: String, - resolved_by: Number, - resolved_at: Date -}); - -ReportSchema.methods.resolve = async function (pid, note) { - this.set('resolved', true); - this.set('resolved_by', pid); - this.set('resolved_at', new Date()); - this.set('note', note); - await this.save(); -}; - -export const REPORT = model('REPORT', ReportSchema); diff --git a/apps/juxtaposition-ui/src/models/report.ts b/apps/juxtaposition-ui/src/models/report.ts new file mode 100644 index 00000000..e8b19f7f --- /dev/null +++ b/apps/juxtaposition-ui/src/models/report.ts @@ -0,0 +1,68 @@ +import { Schema, model } from 'mongoose'; +import type { HydratedDocument, Model } from 'mongoose'; + +export interface IReport { + pid: number; + reported_by: number; + post_id: string; + reason: number; + message: string; + created_at: Date; + resolved?: boolean | null; + note?: string | null; + resolved_by?: number | null; + resolved_at?: Date | null; +} + +export type ReportModel = Model; +export type HydratedReportDocument = HydratedDocument; + +export interface IReportMethods { + resolve(pid: number, note: string | null): Promise; +} + +export const ReportSchema = new Schema({ + pid: { + type: Number, + required: true + }, + reported_by: { + type: Number, + required: true + }, + post_id: { + type: String, + required: true + }, + reason: { + type: Number, + required: true + }, + message: { + type: String, + required: true + }, + created_at: { + type: Date, + required: true, + default: new Date() + }, + resolved: { + type: Boolean, + default: false + }, + note: String, + resolved_by: Number, + resolved_at: Date +}); + +ReportSchema.method('resolve', async function (pid, note) { + this.set('resolved', true); + this.set('resolved_by', pid); + this.set('resolved_at', new Date()); + this.set('note', note); + await this.save(); +}); + +export const Report = model('Report', ReportSchema); +export const REPORT = Report; diff --git a/apps/miiverse-api/src/models/report.ts b/apps/miiverse-api/src/models/report.ts index 436b645f..596eee92 100644 --- a/apps/miiverse-api/src/models/report.ts +++ b/apps/miiverse-api/src/models/report.ts @@ -1,14 +1,39 @@ import { Schema, model } from 'mongoose'; import type { IReport, ReportModel } from '@/types/mongoose/report'; -const ReportSchema = new Schema({ - pid: String, - post_id: String, - reason: Number, +export const ReportSchema = new Schema({ + pid: { + type: Number, + required: true + }, + reported_by: { + type: Number, + required: true + }, + post_id: { + type: String, + required: true + }, + reason: { + type: Number, + required: true + }, + message: { + type: String, + required: true + }, created_at: { type: Date, + required: true, default: new Date() - } + }, + resolved: { + type: Boolean, + default: false + }, + note: String, + resolved_by: Number, + resolved_at: Date }); export const Report = model('Report', ReportSchema); diff --git a/apps/miiverse-api/src/types/mongoose/report.ts b/apps/miiverse-api/src/types/mongoose/report.ts index 413633a3..b6bd6178 100644 --- a/apps/miiverse-api/src/types/mongoose/report.ts +++ b/apps/miiverse-api/src/types/mongoose/report.ts @@ -1,10 +1,16 @@ import type { Model, HydratedDocument } from 'mongoose'; export interface IReport { - pid: string; + pid: number; + reported_by: number; post_id: string; reason: number; + message: string; created_at: Date; + resolved?: boolean | null; + note?: string | null; + resolved_by?: number | null; + resolved_at?: Date | null; } export type ReportModel = Model; From 55d476c6c49d37c835d6a4c65d47408cf0170990 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 4 Jan 2026 13:06:30 +0100 Subject: [PATCH 006/155] feat: port settings model to typescript --- apps/juxtaposition-ui/src/models/settings.js | 110 ------------ apps/juxtaposition-ui/src/models/settings.ts | 168 +++++++++++++++++++ 2 files changed, 168 insertions(+), 110 deletions(-) delete mode 100644 apps/juxtaposition-ui/src/models/settings.js create mode 100644 apps/juxtaposition-ui/src/models/settings.ts diff --git a/apps/juxtaposition-ui/src/models/settings.js b/apps/juxtaposition-ui/src/models/settings.js deleted file mode 100644 index 08a6539b..00000000 --- a/apps/juxtaposition-ui/src/models/settings.js +++ /dev/null @@ -1,110 +0,0 @@ -import { Schema, model } from 'mongoose'; - -export const SettingsSchema = new Schema({ - pid: { type: Number, required: true }, - screen_name: { type: String, required: true }, - account_status: { - type: Number, - default: 0 - }, - // TODO: Move bans to their own collection. User settings should be scoped to older the user - ban_lift_date: { - type: Date, - default: null - }, - banned_by: { - type: Number, - default: null - }, - ban_reason: { - type: String, - default: null - }, - profile_comment: { - type: String, - default: null - }, - profile_comment_visibility: { - type: Boolean, - default: true - }, - game_skill: { - type: Number, - default: 0 - }, - game_skill_visibility: { - type: Boolean, - default: true - }, - birthday_visibility: { - type: Boolean, - default: false - }, - relationship_visibility: { - type: Boolean, - default: false - }, - country_visibility: { - type: Boolean, - default: false - }, - profile_favorite_community_visibility: { - type: Boolean, - default: true - }, - receive_notifications: { - type: Boolean, - default: true - }, - created_at: { - type: Date, - default: Date.now() - }, - last_active: { - type: Date, - default: Date.now(), - index: true - } -}); - -SettingsSchema.methods.updateComment = async function (comment) { - this.set('profile_comment', comment); - await this.save(); -}; - -SettingsSchema.methods.updateSkill = async function (skill) { - this.set('game_skill', skill); - await this.save(); -}; - -SettingsSchema.methods.commentVisible = async function (active) { - this.set('profile_comment_visibility', active); - await this.save(); -}; - -SettingsSchema.methods.skillVisible = async function (active) { - this.set('game_skill_visibility', active); - await this.save(); -}; - -SettingsSchema.methods.birthdayVisible = async function (active) { - this.set('birthday_visibility', active); - await this.save(); -}; - -SettingsSchema.methods.relationshipVisible = async function (active) { - this.set('relationship_visibility', active); - await this.save(); -}; - -SettingsSchema.methods.countryVisible = async function (active) { - this.set('country_visibility', active); - await this.save(); -}; - -SettingsSchema.methods.favCommunityVisible = async function (active) { - this.set('profile_favorite_community_visibility', active); - await this.save(); -}; - -export const SETTINGS = model('SETTINGS', SettingsSchema); diff --git a/apps/juxtaposition-ui/src/models/settings.ts b/apps/juxtaposition-ui/src/models/settings.ts new file mode 100644 index 00000000..f10ed474 --- /dev/null +++ b/apps/juxtaposition-ui/src/models/settings.ts @@ -0,0 +1,168 @@ +import { Schema, model } from 'mongoose'; +import type { HydratedDocument, Model } from 'mongoose'; + +/* This type needs to reflect "reality" as it is in the DB + * Thus, all the optionals, since some legacy documents are missing many fields + */ +export interface ISettings { + pid: number; + screen_name: string; + account_status: number; + ban_lift_date?: Date | null; + banned_by?: number | null; + ban_reason?: string | null; + profile_comment?: string | null; + profile_comment_visibility: boolean; + game_skill: number; + game_skill_visibility: boolean; + birthday_visibility: boolean; + relationship_visibility: boolean; + country_visibility: boolean; + profile_favorite_community_visibility: boolean; + receive_notifications: boolean; + created_at?: Date; + last_active?: Date; +} +// Fields that have "default: " in the Mongoose schema should also be listed here to make them optional +// on input but not output +type SettingsDefaultedFields = + 'account_status' | + 'ban_lift_date' | + 'banned_by' | + 'ban_reason' | + 'profile_comment' | + 'profile_comment_visibility' | + 'game_skill' | + 'game_skill_visibility' | + 'birthday_visibility' | + 'relationship_visibility' | + 'country_visibility' | + 'profile_favorite_community_visibility' | + 'receive_notifications' | + 'created_at' | + 'last_active'; +export type ISettingsInput = Omit & Partial>; + +export interface ISettingsMethods { + updateComment(comment: string | null): Promise; + updateSkill(skill: number): Promise; + commentVisible(active: boolean): Promise; + skillVisible(active: boolean): Promise; + birthdayVisible(active: boolean): Promise; + relationshipVisible(active: boolean): Promise; + countryVisible(active: boolean): Promise; + favCommunityVisible(active: boolean): Promise; +} + +export type SettingsModel = Model; +export type HydratedSettingsDocument = HydratedDocument; + +export const SettingsSchema = new Schema({ + pid: { type: Number, required: true }, + screen_name: { type: String, required: true }, + account_status: { + type: Number, + default: 0 + }, + // TODO: Move bans to their own collection. User settings should be scoped to older the user + ban_lift_date: { + type: Date, + default: null + }, + banned_by: { + type: Number, + default: null + }, + ban_reason: { + type: String, + default: null + }, + profile_comment: { + type: String, + default: null + }, + profile_comment_visibility: { + type: Boolean, + default: true + }, + game_skill: { + type: Number, + default: 0 + }, + game_skill_visibility: { + type: Boolean, + default: true + }, + birthday_visibility: { + type: Boolean, + default: false + }, + relationship_visibility: { + type: Boolean, + default: false + }, + country_visibility: { + type: Boolean, + default: false + }, + profile_favorite_community_visibility: { + type: Boolean, + default: true + }, + receive_notifications: { + type: Boolean, + default: true + }, + created_at: { + type: Date, + default: Date.now() + }, + last_active: { + type: Date, + default: Date.now(), + index: true + } +}); + +SettingsSchema.method('updateComment', async function (comment) { + this.set('profile_comment', comment); + await this.save(); +}); + +SettingsSchema.method('updateSkill', async function (skill) { + this.set('game_skill', skill); + await this.save(); +}); + +SettingsSchema.method('commentVisible', async function (active) { + this.set('profile_comment_visibility', active); + await this.save(); +}); + +SettingsSchema.method('skillVisible', async function (active) { + this.set('game_skill_visibility', active); + await this.save(); +}); + +SettingsSchema.method('birthdayVisible', async function (active) { + this.set('birthday_visibility', active); + await this.save(); +}); + +SettingsSchema.method('relationshipVisible', async function (active) { + this.set('relationship_visibility', active); + await this.save(); +}); + +SettingsSchema.method('countryVisible', async function (active) { + this.set('country_visibility', active); + await this.save(); +}); + +SettingsSchema.method('favCommunityVisible', async function (active) { + this.set('profile_favorite_community_visibility', active); + await this.save(); +}); + +export const Settings = model('Setting', SettingsSchema); +export const SETTINGS = Settings; From 130d09af3e23c17f9b3debd0928e7ff7d730ce47 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 4 Jan 2026 13:08:47 +0100 Subject: [PATCH 007/155] feat: port web.js router to typescript --- .../juxt-web/routes/console/{web.js => web.ts} | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) rename apps/juxtaposition-ui/src/services/juxt-web/routes/console/{web.js => web.ts} (80%) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/web.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/web.ts similarity index 80% rename from apps/juxtaposition-ui/src/services/juxt-web/routes/console/web.js rename to apps/juxtaposition-ui/src/services/juxt-web/routes/console/web.ts index 1ce1b5c7..55048200 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/web.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/web.ts @@ -11,31 +11,36 @@ webRouter.get('/', function (req, res) { }); webRouter.get('/css/:filename', function (req, res) { + const dir = req.directory ?? 'web'; res.set('Content-Type', 'text/css'); res.set('Cache-Control', `public, max-age=${maxAge}`); - res.sendFile('/css/' + req.params.filename, { root: path.join(distFolder, 'webfiles', req.directory) }); + res.sendFile('/css/' + req.params.filename, { root: path.join(distFolder, 'webfiles', dir) }); }); webRouter.get('/js/:filename', function (req, res) { + const dir = req.directory ?? 'web'; res.set('Content-Type', 'application/javascript; charset=utf-8'); res.set('Cache-Control', `public, max-age=${maxAge}`); - res.sendFile('/js/' + req.params.filename, { root: path.join(distFolder, 'webfiles', req.directory) }); + res.sendFile('/js/' + req.params.filename, { root: path.join(distFolder, 'webfiles', dir) }); }); webRouter.get('/images/:filename', function (req, res) { + const dir = req.directory ?? 'web'; res.set('Content-Type', 'image/png'); res.set('Cache-Control', `public, max-age=${maxAge}`); - res.sendFile('/images/' + req.params.filename, { root: path.join(distFolder, 'webfiles', req.directory) }); + res.sendFile('/images/' + req.params.filename, { root: path.join(distFolder, 'webfiles', dir) }); }); webRouter.get('/fonts/:filename', function (req, res) { + const dir = req.directory ?? 'web'; res.set('Content-Type', 'font/woff'); res.set('Cache-Control', `public, max-age=${maxAge}`); - res.sendFile('/fonts/' + req.params.filename, { root: path.join(distFolder, 'webfiles', req.directory) }); + res.sendFile('/fonts/' + req.params.filename, { root: path.join(distFolder, 'webfiles', dir) }); }); webRouter.get('/favicon.ico', function (req, res) { + const dir = req.directory ?? 'web'; res.set('Content-Type', 'image/x-icon'); res.set('Cache-Control', `public, max-age=${maxAge}`); - res.sendFile('/images/favicon.ico', { root: path.join(distFolder, 'webfiles', req.directory) }); + res.sendFile('/images/favicon.ico', { root: path.join(distFolder, 'webfiles', dir) }); }); From 1464671bcb3f697c354ff2dcfc7b7c049b45a2df Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 4 Jan 2026 13:18:56 +0100 Subject: [PATCH 008/155] feat: port show.js to typescript --- .../services/juxt-web/routes/console/show.js | 53 ------------- .../services/juxt-web/routes/console/show.tsx | 76 +++++++++++++++++++ .../services/juxt-web/routes/routeUtils.ts | 11 ++- 3 files changed, 86 insertions(+), 54 deletions(-) delete mode 100644 apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.js create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.tsx diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.js deleted file mode 100644 index 9dc03c3f..00000000 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.js +++ /dev/null @@ -1,53 +0,0 @@ -import express from 'express'; -import { database } from '@/database'; -import { createUser, setName } from '@/util'; -export const showRouter = express.Router(); - -showRouter.get('/', async function (req, res) { - if (req.pid === 1000000000) { - return res.render(req.directory + '/guest_notice.ejs'); - } - - const user = await database.getUserSettings(req.pid); - const content = await database.getUserContent(req.pid); - if (!user || !content) { - return res.render(req.directory + '/first_run.ejs'); - } - - if (req.query.topic_tag) { - res.redirect(`/topics?topic_tag=${req.query.topic_tag}`); - } else if (req.query.pid) { - res.redirect(`/users/${req.query.pid}`); - } else { - res.redirect('/titles'); - } - - const usrMii = await database.getUserSettings(req.pid); - if (req.user.mii.name !== usrMii.screen_name) { - setName(req.pid, req.user.mii.name); - usrMii.screen_name = req.user.mii.name; - await usrMii.save(); - } -}); - -showRouter.get('/first', async function (req, res) { - res.render(req.directory + '/first_run.ejs'); -}); - -showRouter.post('/newUser', async function (req, res) { - if (req.pid === null || !req.new_users || req.directory === 'web') { - return res.sendStatus(401); - } - - const user = await database.getUserSettings(req.pid); - if (user) { - return res.sendStatus(504); - } - - await createUser(req.pid, req.body.experience, req.body.notifications); - if (await database.getUserSettings(req.pid) !== null) { - res.sendStatus(200); - } else { - res.sendStatus(504); - } -}); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.tsx new file mode 100644 index 00000000..6dd68205 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/show.tsx @@ -0,0 +1,76 @@ +import express from 'express'; +import z from 'zod'; +import { database } from '@/database'; +import { createUser, setName } from '@/util'; +import { parseReq } from '@/services/juxt-web/routes/routeUtils'; + +export const showRouter = express.Router(); + +showRouter.get('/', async function (req, res) { + const { auth, hasAuth, query } = parseReq(req, { + query: z.object({ + topic_tag: z.string().optional(), + pid: z.string().optional() + }) + }); + + if (req.pid === 1000000000) { + return res.render(req.directory + '/guest_notice.ejs'); + } + + const user = await database.getUserSettings(req.pid); + const content = await database.getUserContent(req.pid); + if (!user || !content) { + return res.render(req.directory + '/first_run.ejs'); + } + + if (query.topic_tag) { + res.redirect(`/topics?topic_tag=${query.topic_tag}`); + } else if (query.pid) { + res.redirect(`/users/${query.pid}`); + } else { + res.redirect('/titles'); + } + + if (hasAuth()) { + const currentMii = auth().user.mii; + const usrMii = await database.getUserSettings(req.pid); + if (!currentMii || !usrMii) { + return; + } + if (currentMii.name !== usrMii.screen_name) { + setName(auth().pid, currentMii.name); + usrMii.screen_name = currentMii.name; + await usrMii.save(); + } + } +}); + +showRouter.get('/first', async function (req, res) { + res.render(req.directory + '/first_run.ejs'); +}); + +showRouter.post('/newUser', async function (req, res) { + const { auth, body } = parseReq(req, { + body: z.object({ + experience: z.number(), + notifications: z.boolean() + }) + }); + + if (req.pid === null || !req.new_users || req.directory === 'web') { + return res.sendStatus(401); + } + + const user = await database.getUserSettings(auth().pid); + if (user) { + return res.sendStatus(504); // User already exists + } + + await createUser(auth().pid, body.experience, body.notifications); + if (await database.getUserSettings(auth().pid) !== null) { + res.sendStatus(200); + } else { + res.sendStatus(504); + } +}); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts b/apps/juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts index 20ba23c6..64193063 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts @@ -29,6 +29,7 @@ export type ParsedRequest = { body: TBody extends z.ZodType ? z.infer : undefined; query: TQuery extends z.ZodType ? z.infer : undefined; auth: () => AuthContext; + hasAuth: () => boolean; }; export function getAuthedRequest(req: TReq): AuthRequest { @@ -69,9 +70,17 @@ export function parseReq; } From 2c89b3be1b397c83b79f67d40b445dc1ae1ae331 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 7 Jan 2026 14:53:58 +0100 Subject: [PATCH 009/155] feat: port first_run.ejs to jsx --- .../juxt-web/views/ctr/firstRunView.tsx | 255 ++++++++++++++++++ .../src/services/juxt-web/views/ctr/root.tsx | 3 +- .../juxt-web/views/portal/firstRunView.tsx | 232 ++++++++++++++++ .../juxt-web/views/web/firstRunView.tsx | 20 ++ .../src/services/juxt-web/views/web/login.tsx | 61 +++++ .../src/services/juxt-web/views/web/root.tsx | 4 +- .../src/webfiles/ctr/css/firstrun.css | 72 +++++ .../src/webfiles/ctr/js/firstrun.js | 33 +++ .../src/webfiles/portal/css/firstrun.css | 83 ++++++ .../src/webfiles/portal/js/firstrun.js | 40 +++ apps/juxtaposition-ui/tsup.config.ts | 6 +- 11 files changed, 802 insertions(+), 7 deletions(-) create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/ctr/firstRunView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/portal/firstRunView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/firstRunView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/login.tsx create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/css/firstrun.css create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/js/firstrun.js create mode 100644 apps/juxtaposition-ui/src/webfiles/portal/css/firstrun.css create mode 100644 apps/juxtaposition-ui/src/webfiles/portal/js/firstrun.js diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/firstRunView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/firstRunView.tsx new file mode 100644 index 00000000..81a3d550 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/firstRunView.tsx @@ -0,0 +1,255 @@ +import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root'; +import type { ReactNode } from 'react'; +import type { FirstRunViewProps } from '@/services/juxt-web/views/web/firstRunView'; + +const AboutSection = { + Root(props: { children?: ReactNode; id?: string }): ReactNode { + return ( +
+ {props.children} +
+ ); + }, + Title(props: { children?: ReactNode }): ReactNode { + return ( +

{props.children}

+ ); + }, + Body(props: { children?: ReactNode }): ReactNode { + return ( +
+ {props.children} +
+ ); + }, + BackButton(props: { currentId: string; onClick?: string; previousId?: string; text: string }): ReactNode { + return ( + + ); + }, + NextButton(props: { currentId: string; onClick?: string; nextId?: string; text: string; sound?: string }): ReactNode { + return ( + + ); + } +}; + +export function CtrFirstRunView(props: FirstRunViewProps): ReactNode { + const sections = { + welcome: 'welcome', + beta: 'beta', + about: 'about', + manners: 'manners', + ga: 'google-analytics', + experience: 'game-experience', + ready: 'ready', + end: 'have-fun' + }; + + const head = ( + <> + + + + ); + + return ( + + + +
+
+ + + {props.ctx.lang.setup.welcome} + +

+ {props.ctx.lang.setup.welcome_text} +

+
+ + + +
+ + + {props.ctx.lang.setup.beta} + +

+ {props.ctx.lang.setup.beta_text.first} +
+ {props.ctx.lang.setup.beta_text.second} +
+ {props.ctx.lang.setup.beta_text.third} +

+
+ + + +
+ + + {props.ctx.lang.setup.info} + +

+ {props.ctx.lang.setup.info_text} +

+
+ + + +
+ + + {props.ctx.lang.setup.rules} + +

+ { props.ctx.lang.setup.rules_text.first } +
+
+

+

{ props.ctx.lang.setup.rules_text.second }

+
+ { props.ctx.lang.setup.rules_text.third } +
+
+
+

{ props.ctx.lang.setup.rules_text.fourth }

+
+ { props.ctx.lang.setup.rules_text.fifth } +
+
+
+

{ props.ctx.lang.setup.rules_text.sixth }

+
+ { props.ctx.lang.setup.rules_text.seventh } +
+
+
+

{ props.ctx.lang.setup.rules_text.eighth }

+
+ { props.ctx.lang.setup.rules_text.ninth } +
+
+
+

{ props.ctx.lang.setup.rules_text.tenth }

+
+ { props.ctx.lang.setup.rules_text.eleventh } +
+
+
+

{ props.ctx.lang.setup.rules_text.twelfth }

+
+ { props.ctx.lang.setup.rules_text.thirteenth } +
+
+

+
+ + + +
+ + + {props.ctx.lang.setup.google} + +

+ { props.ctx.lang.setup.google_text } +

+
+ + + +
+ + + {props.ctx.lang.setup.experience} + +

+ { props.ctx.lang.setup.experience_text.info } +

+
+ + + + +
+ + + {props.ctx.lang.setup.ready} + +

+ { props.ctx.lang.setup.ready_text } +

+
+ + + +
+ + + + + {props.ctx.lang.setup.done} + +
+
+
+
+
+ +
+
+
+
+
+
+ ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/root.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/root.tsx index 4a2bf3f2..d2b2cb1d 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/root.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/root.tsx @@ -22,6 +22,7 @@ export type HtmlProps = { children?: ReactNode; head?: ReactNode; title: string; + onLoad?: string; }; export function CtrRoot(props: HtmlProps): ReactNode { @@ -32,7 +33,7 @@ export function CtrRoot(props: HtmlProps): ReactNode { {props.title} {props.head} - {props.children} + {props.children} ); } diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/firstRunView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/firstRunView.tsx new file mode 100644 index 00000000..28339751 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/firstRunView.tsx @@ -0,0 +1,232 @@ +import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root'; +import type { ReactNode } from 'react'; +import type { FirstRunViewProps } from '@/services/juxt-web/views/web/firstRunView'; + +const AboutSection = { + Root(props: { children?: ReactNode; id?: string }): ReactNode { + return ( +
+ {props.children} +
+ ); + }, + Title(props: { children?: ReactNode }): ReactNode { + return ( +

{props.children}

+ ); + }, + Body(props: { children?: ReactNode }): ReactNode { + return ( +
+ {props.children} +
+ ); + }, + BackButton(props: { currentId: string; onClick?: string; previousId?: string; text: string }): ReactNode { + return ( + + ); + }, + NextButton(props: { currentId: string; onClick?: string; nextId?: string; text: string; sound?: string }): ReactNode { + return ( + + ); + } +}; + +export function PortalFirstRunView(props: FirstRunViewProps): ReactNode { + const sections = { + welcome: 'welcome', + beta: 'beta', + about: 'about', + manners: 'manners', + ga: 'google-analytics', + experience: 'game-experience', + ready: 'ready', + end: 'have-fun' + }; + + const head = ( + <> + + + + ); + + return ( + + + + {props.ctx.lang.setup.welcome} + +

+ {props.ctx.lang.setup.welcome_text} +

+
+ + + +
+ + + {props.ctx.lang.setup.beta} + +

+ {props.ctx.lang.setup.beta_text.first} +
+ {props.ctx.lang.setup.beta_text.second} +
+ {props.ctx.lang.setup.beta_text.third} +

+
+ + + +
+ + + {props.ctx.lang.setup.info} + +

+ {props.ctx.lang.setup.info_text} +

+
+ + + +
+ + + {props.ctx.lang.setup.rules} + +

+ { props.ctx.lang.setup.rules_text.first } +
+
+

+

{ props.ctx.lang.setup.rules_text.second }

+
+ { props.ctx.lang.setup.rules_text.third } +
+
+
+

{ props.ctx.lang.setup.rules_text.fourth }

+
+ { props.ctx.lang.setup.rules_text.fifth } +
+
+
+

{ props.ctx.lang.setup.rules_text.sixth }

+
+ { props.ctx.lang.setup.rules_text.seventh } +
+
+
+

{ props.ctx.lang.setup.rules_text.eighth }

+
+ { props.ctx.lang.setup.rules_text.ninth } +
+
+
+

{ props.ctx.lang.setup.rules_text.tenth }

+
+ { props.ctx.lang.setup.rules_text.eleventh } +
+
+
+

{ props.ctx.lang.setup.rules_text.twelfth }

+
+ { props.ctx.lang.setup.rules_text.thirteenth } +
+
+

+
+ + + +
+ + + {props.ctx.lang.setup.google} + +

+ { props.ctx.lang.setup.google_text } +

+
+ + + +
+ + + {props.ctx.lang.setup.experience} + +

+ { props.ctx.lang.setup.experience_text.info } +

+
+ + + + +
+ + + {props.ctx.lang.setup.ready} + +

+ { props.ctx.lang.setup.ready_text } +

+
+ + + +
+ + + {props.ctx.lang.setup.done} +
+
+
+
+
+ +
+
+
+
+ ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/firstRunView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/firstRunView.tsx new file mode 100644 index 00000000..ee7c9c37 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/firstRunView.tsx @@ -0,0 +1,20 @@ +import { WebLoginRoot } from '@/services/juxt-web/views/web/login'; +import type { ReactNode } from 'react'; +import type { RenderContext } from '@/services/juxt-web/views/context'; + +export type FirstRunViewProps = { + ctx: RenderContext; +}; + +export function WebFirstRunView(_props: FirstRunViewProps): ReactNode { + const extraHead = Juxtaposition Log In; + return ( + +
+
+

Account Creation is only available when you have a linked Wii U or 3DS.

+
+
+
+ ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/login.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/login.tsx new file mode 100644 index 00000000..f14b4b43 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/login.tsx @@ -0,0 +1,61 @@ +import { DefaultHead } from '@/services/juxt-web/views/web/root'; +import type { ReactNode } from 'react'; +import type { HtmlProps } from '@/services/juxt-web/views/web/root'; + +export function LoginHead(): ReactNode { + return ( + <> + {/* windows/ios/chrome */} + + + + + + + {/* open graph/embeds */} + + + + + + + + + {/* twitter embeds */} + + + + + + + + {/* google seo */} + + + + {/* favicon */} + + + + + + + ); +} + +export function WebLoginRoot(props: HtmlProps): ReactNode { + return ( + + + + + {props.head} + + +
+ {props.children} +
+ + + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/root.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/root.tsx index 4826f828..9c66d917 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/root.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/root.tsx @@ -1,13 +1,15 @@ import { InlineScript } from '@/services/juxt-web/views/common'; import type { ReactNode } from 'react'; -function DefaultHead(): ReactNode { +export function DefaultHead(): ReactNode { return ( <> + - -
- -
-
-
-

<%= lang.setup.welcome %>

-
-

- <%= lang.setup.welcome_text %> -

-
- - - -
- -
-

<%= lang.setup.beta %>

-
-

- <%= lang.setup.beta_text.first %> -
- <%= lang.setup.beta_text.second %> -
- <%= lang.setup.beta_text.third %> -

-
- - -
- -
-

<%= lang.setup.info %>

-
-

- <%= lang.setup.info_text %> -

-
- - -
- -
-

<%= lang.setup.rules %>

-
-

- <%= lang.setup.rules_text.first %> -

-

-

<%= lang.setup.rules_text.second %>

-
- <%= lang.setup.rules_text.third %> -

-
-

<%= lang.setup.rules_text.fourth %>

-
- <%= lang.setup.rules_text.fifth %> -

-
-

<%= lang.setup.rules_text.sixth %>

-
- <%= lang.setup.rules_text.seventh %> -

-
-

<%= lang.setup.rules_text.eighth %>

-
- <%= lang.setup.rules_text.ninth %> -

-
-

<%= lang.setup.rules_text.tenth %>

-
- <%= lang.setup.rules_text.eleventh %> -

-
-

<%= lang.setup.rules_text.twelfth %>

-
- <%= lang.setup.rules_text.thirteenth %> -

-

-
- - -
- -
-

<%= lang.setup.google %>

-
-

- <%= lang.setup.google_text %> -

-
- - -
- -
-

<%= lang.setup.experience %>

-
-

- <%= lang.setup.experience_text.info %> -

-
- - - -
- -
-

<%= lang.setup.ready %>

-
-

- <%= lang.setup.ready_text %> -

-
- - -
- -
-

-
-

<%= lang.setup.done %>

-
-



-
- -
-
-
-
-
- - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/webfiles/portal/first_run.ejs b/apps/juxtaposition-ui/src/webfiles/portal/first_run.ejs deleted file mode 100644 index 697d7efb..00000000 --- a/apps/juxtaposition-ui/src/webfiles/portal/first_run.ejs +++ /dev/null @@ -1,309 +0,0 @@ - - -<%- include('partials/head', { title: '' }); %> - - - -
-
-

<%= lang.setup.welcome %>

-
-

- <%= lang.setup.welcome_text %> -

-
- - -
- -
-

<%= lang.setup.beta %>

-
-

- <%= lang.setup.beta_text.first %> -
- <%= lang.setup.beta_text.second %> -
- <%= lang.setup.beta_text.third %> -

-
- - -
- -
-

<%= lang.setup.info %>

-
-

- <%= lang.setup.info_text %> -

-
- - -
- -
-

<%= lang.setup.rules %>

-
-

- <%= lang.setup.rules_text.first %> -

-

-

<%= lang.setup.rules_text.second %>

-
- <%= lang.setup.rules_text.third %> -

-
-

<%= lang.setup.rules_text.fourth %>

-
- <%= lang.setup.rules_text.fifth %> -

-
-

<%= lang.setup.rules_text.sixth %>

-
- <%= lang.setup.rules_text.seventh %> -

-
-

<%= lang.setup.rules_text.eighth %>

-
- <%= lang.setup.rules_text.ninth %> -

-
-

<%= lang.setup.rules_text.tenth %>

-
- <%= lang.setup.rules_text.eleventh %> -

-
-

<%= lang.setup.rules_text.twelfth %>

-
- <%= lang.setup.rules_text.thirteenth %> -

-

-
- - -
- -
-

<%= lang.setup.google %>

-
-

- <%= lang.setup.google_text %> -

-
- - -
- -
-

<%= lang.setup.experience %>

-
-

- <%= lang.setup.experience_text.info %> -

-
- - - -
- -
-

<%= lang.setup.ready %>

-
-

- <%= lang.setup.ready_text %> -

-
- - -
- -
-

-
-

<%= lang.setup.done %>

-
-



-
- -
- -
-
- - - diff --git a/apps/juxtaposition-ui/src/webfiles/web/first_run.ejs b/apps/juxtaposition-ui/src/webfiles/web/first_run.ejs deleted file mode 100644 index a5359ace..00000000 --- a/apps/juxtaposition-ui/src/webfiles/web/first_run.ejs +++ /dev/null @@ -1,285 +0,0 @@ - - - - - Juxtaposition Log In - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
-
- \ No newline at end of file From ee56fd24204fa8ec902c5902c7052230b0753bd9 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 7 Jan 2026 15:08:26 +0100 Subject: [PATCH 011/155] feat: port web routers to ts --- .../juxt-web/routes/web/{login.js => login.tsx} | 13 +++++-------- .../services/juxt-web/routes/web/{pwa.js => pwa.ts} | 0 .../juxt-web/routes/web/{robots.js => robots.ts} | 0 3 files changed, 5 insertions(+), 8 deletions(-) rename apps/juxtaposition-ui/src/services/juxt-web/routes/web/{login.js => login.tsx} (92%) rename apps/juxtaposition-ui/src/services/juxt-web/routes/web/{pwa.js => pwa.ts} (100%) rename apps/juxtaposition-ui/src/services/juxt-web/routes/web/{robots.js => robots.ts} (100%) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx similarity index 92% rename from apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.js rename to apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx index 8777bef1..71598884 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx @@ -36,14 +36,11 @@ loginRouter.post('/', async (req, res) => { return res.render(req.directory + '/login.ejs', { toast: 'Invalid username or password.', redirect }); } - let discovery = await database.getEndPoint(config.serverEnvironment); - if (!discovery) { - discovery = { - status: 5 - }; - } + const discovery = await database.getEndPoint(config.serverEnvironment); + const discoveryStatus = discovery?.status ?? 5; + let message = ''; - switch (discovery.status) { + switch (discoveryStatus) { case 3: message = 'Juxt is currently undergoing maintenance. Please try again later.'; break; @@ -54,7 +51,7 @@ loginRouter.post('/', async (req, res) => { message = 'Juxt is currently unavailable. Please try again later.'; break; } - if (discovery.status !== 0) { + if (discoveryStatus !== 0) { return res.render(req.directory + '/error.ejs', { code: 504, message: message diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/pwa.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/pwa.ts similarity index 100% rename from apps/juxtaposition-ui/src/services/juxt-web/routes/web/pwa.js rename to apps/juxtaposition-ui/src/services/juxt-web/routes/web/pwa.ts diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/robots.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/robots.ts similarity index 100% rename from apps/juxtaposition-ui/src/services/juxt-web/routes/web/robots.js rename to apps/juxtaposition-ui/src/services/juxt-web/routes/web/robots.ts From 92e72177aff8509870b4195d3ada22110c8b533b Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 7 Jan 2026 15:22:49 +0100 Subject: [PATCH 012/155] feat: port login page to JSX --- .../middleware/{checkBan.ts => checkBan.tsx} | 9 ++- .../{discovery.ts => discovery.tsx} | 4 +- .../services/juxt-web/routes/web/login.tsx | 28 +++++++-- .../src/services/juxt-web/views/web/login.tsx | 1 + .../services/juxt-web/views/web/loginView.tsx | 61 +++++++++++++++++++ 5 files changed, 91 insertions(+), 12 deletions(-) rename apps/juxtaposition-ui/src/middleware/{checkBan.ts => checkBan.tsx} (88%) rename apps/juxtaposition-ui/src/middleware/{discovery.ts => discovery.tsx} (80%) create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/loginView.tsx diff --git a/apps/juxtaposition-ui/src/middleware/checkBan.ts b/apps/juxtaposition-ui/src/middleware/checkBan.tsx similarity index 88% rename from apps/juxtaposition-ui/src/middleware/checkBan.ts rename to apps/juxtaposition-ui/src/middleware/checkBan.tsx index 5cae5f5e..320e85e7 100644 --- a/apps/juxtaposition-ui/src/middleware/checkBan.ts +++ b/apps/juxtaposition-ui/src/middleware/checkBan.tsx @@ -2,6 +2,8 @@ import moment from 'moment'; import { database as db } from '@/database'; import { config } from '@/config'; import { humanDate, humanFromNow } from '@/util'; +import { WebLoginView } from '@/services/juxt-web/views/web/loginView'; +import { buildContext } from '@/services/juxt-web/views/context'; import type { RequestHandler } from 'express'; export const checkBan: RequestHandler = async (request, response, next) => { @@ -42,7 +44,7 @@ export const checkBan: RequestHandler = async (request, response, next) => { if (!accessAllowed) { response.status(500); if (request.directory === 'web') { - return response.render('web/login.ejs', { toast: 'No access. Must be tester or dev', redirect: request.originalUrl }); + return response.jsx(); } else { return response.render('portal/error_fatal.ejs', { code: 5989999, @@ -80,10 +82,7 @@ export const checkBan: RequestHandler = async (request, response, next) => { banMessage += `\n\nIf you have any questions, please contact the moderators on the Pretendo Network Forum (forum.pretendo.network).`; if (request.directory === 'web') { - return response.render('web/login.ejs', { - toast: banMessage, - redirect: request.originalUrl - }); + return response.jsx(); } else { return response.render(request.directory + '/error_fatal.ejs', { message: banMessage, diff --git a/apps/juxtaposition-ui/src/middleware/discovery.ts b/apps/juxtaposition-ui/src/middleware/discovery.tsx similarity index 80% rename from apps/juxtaposition-ui/src/middleware/discovery.ts rename to apps/juxtaposition-ui/src/middleware/discovery.tsx index 04a0b578..1af22e49 100644 --- a/apps/juxtaposition-ui/src/middleware/discovery.ts +++ b/apps/juxtaposition-ui/src/middleware/discovery.tsx @@ -1,5 +1,7 @@ import { database as db } from '@/database'; import { config } from '@/config'; +import { WebLoginView } from '@/services/juxt-web/views/web/loginView'; +import { buildContext } from '@/services/juxt-web/views/context'; import type { RequestHandler } from 'express'; export const checkDiscovery: RequestHandler = async (request, response, next) => { @@ -20,7 +22,7 @@ export const checkDiscovery: RequestHandler = async (request, response, next) => break; } if (request.directory === 'web') { - return response.render('web/login.ejs', { toast: message, redirect: request.originalUrl }); + return response.jsx(); } else { return response.render('portal/error_fatal.ejs', { message, diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx index 71598884..fc77248d 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx @@ -1,29 +1,45 @@ import express from 'express'; +import z from 'zod'; import { database } from '@/database'; import { passwordLogin, getUserDataFromToken } from '@/util'; import { config } from '@/config'; import { logger } from '@/logger'; +import { WebLoginView } from '@/services/juxt-web/views/web/loginView'; +import { buildContext } from '@/services/juxt-web/views/context'; +import { parseReq } from '@/services/juxt-web/routes/routeUtils'; export const loginRouter = express.Router(); const cookieDomain = config.http.cookieDomain; loginRouter.get('/', async function (req, res) { - res.render(req.directory + '/login.ejs', { toast: null, redirect: req.query.redirect ?? '/' }); + const { query } = parseReq(req, { + query: z.object({ + redirect: z.string().optional() + }) + }); + return res.jsx(); }); loginRouter.post('/', async (req, res) => { - const { username, password, redirect } = req.body; + const { body } = parseReq(req, { + body: z.object({ + username: z.string(), + password: z.string(), + redirect: z.string().default('/') + }) + }); + const { username, password, redirect } = body; const login = await passwordLogin(username, password).catch((e) => { switch (e.details) { case 'INVALID_ARGUMENT: User not found': - res.render(req.directory + '/login.ejs', { toast: 'Username was invalid.', redirect }); + res.jsx(); break; case 'INVALID_ARGUMENT: Password is incorrect': - res.render(req.directory + '/login.ejs', { toast: 'Password was incorrect.', redirect }); + res.jsx(); break; default: logger.error(e, `Login error for ${username}`); - res.render(req.directory + '/login.ejs', { toast: 'Invalid username or password.', redirect }); + res.jsx(); break; } }); @@ -33,7 +49,7 @@ loginRouter.post('/', async (req, res) => { const PNID = await getUserDataFromToken(login.accessToken); if (!PNID) { - return res.render(req.directory + '/login.ejs', { toast: 'Invalid username or password.', redirect }); + return res.jsx(); } const discovery = await database.getEndPoint(config.serverEnvironment); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/login.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/login.tsx index f14b4b43..bf6d1ba5 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/login.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/login.tsx @@ -39,6 +39,7 @@ export function LoginHead(): ReactNode { + - - - -
-
data-show="true" <% } %>><%= toast %>
-
- -
-
- - \ No newline at end of file From 87209841e66edd63883f5ae4a060ee8499139f71 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 7 Jan 2026 16:23:12 +0100 Subject: [PATCH 019/155] fix: make tabs on web be next to each other --- apps/juxtaposition-ui/src/webfiles/web/css/web.css | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/juxtaposition-ui/src/webfiles/web/css/web.css b/apps/juxtaposition-ui/src/webfiles/web/css/web.css index 35382de2..4a34d928 100644 --- a/apps/juxtaposition-ui/src/webfiles/web/css/web.css +++ b/apps/juxtaposition-ui/src/webfiles/web/css/web.css @@ -1627,7 +1627,6 @@ button.report { .buttons.tabs>a, .buttons.tabs>button { - width: 100%; margin: 0 1ch; border: 1px solid var(--text-secondary); cursor: pointer; From a29e15577d426708f90e0ba287828f60a85fb212 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Fri, 9 Jan 2026 11:15:31 +1100 Subject: [PATCH 020/155] feat: add spritesheet script still unsure if this should be an ESBuild plugin or just run manually --- apps/juxtaposition-ui/package.json | 6 +- apps/juxtaposition-ui/scripts/sprites.ts | 49 ++ .../src/webfiles/ctr/css/sprites.css | 54 ++ .../src/webfiles/ctr/images/sprites.png | Bin 0 -> 7248 bytes .../sprites/feeling-frustrated-unselected.png | Bin 0 -> 1592 bytes .../sprites/feeling-happy-unselected.png | Bin 0 -> 1189 bytes .../sprites/feeling-like-unselected.png | Bin 0 -> 1192 bytes .../sprites/feeling-normal-unselected.png | Bin 0 -> 984 bytes .../sprites/feeling-puzzled-unselected.png | Bin 0 -> 1256 bytes .../sprites/feeling-suprise-unselected.png | Bin 0 -> 1339 bytes package-lock.json | 801 +++++++++++++++++- 11 files changed, 899 insertions(+), 11 deletions(-) create mode 100644 apps/juxtaposition-ui/scripts/sprites.ts create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-normal-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-puzzled-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-suprise-unselected.png diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 4fd4f9d7..4a64642c 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -13,7 +13,8 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "build": "tsup && tsc --noEmit", - "start": "node --enable-source-maps dist/server.js" + "start": "node --enable-source-maps dist/server.js", + "build:sprites": "node scripts/sprites.ts" }, "dependencies": { "@aws-sdk/client-s3": "^3.958.0", @@ -62,6 +63,7 @@ "@types/node": "^22.19.1", "@types/react": "^19.1.13", "@types/react-dom": "^19.2.3", + "@types/spritesmith": "^3.4.5", "browserslist": "^4.28.0", "browserslist-to-esbuild": "^2.1.1", "ejs-lint": "^2.0.1", @@ -71,6 +73,8 @@ "globals": "^16.5.0", "nodemon": "^3.1.11", "npm-run-all": "^4.1.5", + "spritesheet-templates": "^10.5.2", + "spritesmith": "^3.5.1", "tsup": "^8.5.1", "typescript": "^5.9.3" } diff --git a/apps/juxtaposition-ui/scripts/sprites.ts b/apps/juxtaposition-ui/scripts/sprites.ts new file mode 100644 index 00000000..93271a07 --- /dev/null +++ b/apps/juxtaposition-ui/scripts/sprites.ts @@ -0,0 +1,49 @@ +// Load in dependencies +import { readdirSync, writeFileSync } from 'node:fs'; +import { basename } from 'node:path'; +import Spritesmith from 'spritesmith'; +// @ts-expect-error: missing upstream types +import templater from 'spritesheet-templates'; + +const ctr = 'src/webfiles/ctr'; +const ctr_images = ctr + '/images'; +const sprite_paths = readdirSync(ctr_images + '/sprites') + .filter(path => path.endsWith('.png')) + .map(path => ctr_images + '/sprites/' + path); + +// Generate our spritesheet +Spritesmith.run({ + src: sprite_paths +}, function handleResult(err, result) { + if (err) { + console.error(err); + return; + } + const { coordinates, properties, image } = result; + + const sprites = Object.entries(coordinates).map(([name, coords]) => { + return { + name: basename(name, '.png'), + ...coords + }; + }); + + const res = templater({ + sprites, + spritesheet: { + ...properties, + image: '/images/sprites.png' + } + }, { + format: 'css', + formatOpts: { + cssSelector: ({ name }: { name: string }) => '.sprite-' + name + } + }); + + writeFileSync(ctr + '/css/sprites.css', res, { + encoding: 'utf-8' + }); + writeFileSync(ctr + '/images/sprites.png', image); + console.info(`success: wrote ${sprite_paths.length} sprites`); +}); diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css new file mode 100644 index 00000000..cd1882bf --- /dev/null +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css @@ -0,0 +1,54 @@ +/* +Icon classes can be used entirely standalone. They are named after their original file names. + +Example usage in HTML: + +`display: block` sprite: +
+ +To change `display` (e.g. `display: inline-block;`), we suggest using a common CSS class: + +// CSS +.icon { + display: inline-block; +} + +// HTML + +*/ +.sprite-feeling-frustrated-unselected { + background-image: url(/images/sprites.png); + background-position: 0px 0px; + width: 42px; + height: 42px; +} +.sprite-feeling-happy-unselected { + background-image: url(/images/sprites.png); + background-position: -42px 0px; + width: 42px; + height: 42px; +} +.sprite-feeling-like-unselected { + background-image: url(/images/sprites.png); + background-position: 0px -42px; + width: 42px; + height: 42px; +} +.sprite-feeling-normal-unselected { + background-image: url(/images/sprites.png); + background-position: -42px -42px; + width: 42px; + height: 42px; +} +.sprite-feeling-puzzled-unselected { + background-image: url(/images/sprites.png); + background-position: -84px 0px; + width: 42px; + height: 42px; +} +.sprite-feeling-suprise-unselected { + background-image: url(/images/sprites.png); + background-position: -84px -42px; + width: 42px; + height: 42px; +} diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..463f97761ccb01b14148542ce51558990efa0d27 GIT binary patch literal 7248 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 DR_F>G literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated-unselected.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated-unselected.png new file mode 100644 index 0000000000000000000000000000000000000000..f0edc500e2eb2a25d1a6753c45a16984a365f802 GIT binary patch literal 1592 zcmV-82FLk{P)icHj#j56A{$ zLJW5TM&Kq;22?7ggz1Ec%n^~(BGN2xk0ufMUPR_h2SFtw--t+;OdUE!rKw`UN;6%#SB(G(QviH(a6X5Iv3DW$~g(H+4^0@PM_ z^5lV;rn(-A&(vV)9z@(iS#dp;h8MIp_j0|oF{E3rmp0Pc+)Jh51!cwch+D994^n)l zhNik6o;)y9TiqGTd=>B!fOi^uLXAvHo#*kpK%d3V#UE=(Ssst6_LXl;a$0;yqRDCT znA`dRsJq*R)8V4)rHwv|-M6Mmsq?~ZfxSSLcj@l~A+NAURUK56JVj(|bhdm|B8N_` zXI4y9P}|`lH&=c!^1R`7P!<2|fS!#XFAYcZo>FRy(t_TBF!3Y+HI?mDl$boO)sVVk zK3|>Q5R#-n*^-|h(c0V_Kr(-S7MpU@A{{CRFjuEV=3z;STWD>z29N}>^*ZRcIwKz6 z0h^P)Ui+w*LTj@Xw>x4C9SdaWwDch?$+F^l9{<}lMtyBo2N%zJ4p=Byx(B)N!$Yh+ z_Az}=9+>UznV%RN)V{CR!K2C!(pDz0a`i%AyKC6Z z{abC!ij88!wp5e~JL~OAL^1&r?>APBTb}&!;;Drjzc-S+ES`cQz0b>iukDl-*Lzg@ z$ZF;R#bn{iIU_?(7XXH<^P7nftyN07yc_Q3xTap|pe#%Q4eEs3x5dnYsM^D>;+Y$08O3y#WWb7V$B9#hyy@u<{ zTDO?O6WNgt5x`cZ)E%!!tAOTkqpJ)p0VFr)r7KBoxh_q{ zZmJ9|kw`WI)xN-4s|EcO&f?)e?E&7{RhUWP@$3M(N?0_TeaF_a@7P)r7R~mxM@8!> zJf6+2!c4N(Ee`t55IF0tQ~3-2uRvEQ3w_QK&t0}Gp1nucpp;^1Mgj#zdX!EPH+L5M zPpl(u?yP`krBf6X=~6*dyimMUjeO2AO%3kEZ~U4Af}J)l8rr*r1ep6)vwwDP`7D z6Wa`s>;v|UCK;*zl&95#-W9AOlKZw5y^UH)aJtbZA{!=KBTW*KKc|sog@`Pf%F_7Z zDfVt#SP7aW;W!cbPDC0fLC_>3r$l7-wEw=*NN?5DVJDCiCZ2TwwOSNZrj)w#&i*q6 qAsq#zYuf8Ad-VJL5OYoTBmV>Sdvt$L3r9%+0000S(CWRXTu!D%`~E_W!r_UR@G_UJ$Y2MDpbhlb{TR#Y?)DdQq^qWPauovXcRam zB7d(Rv<1kIUq=xV;BlbVKO5NmCY=LbCHguE)C7F>Rs!yiay0#yK)#5~`A78qM+kCd z30&9^QENRofv0%1>10q{IPtTk}jEWyGL zm%8X~a{+Lo!oh2A7aLg{F89!WV+4SsK1Xf}IR&X}+c_thyy7%d(0xFjsA>h!V(MC4Sf;Pjjo5^u z(hO`dW(#e3N$KryqpB2^W)K@^3uRMPh^n3f>Up$v%Elg%4Vc~ooE{r6wg*7R#7a*y zCOdz8WQMEX{6$iVoipd2X5aqUke)XGV3E47y778^yk2vJ15YFx*?>fw)g0(BakMrM zGBV(yx5G`-cfBEprSaQddOO^V40vd59yGPx7Llcu&2-JEoXJ_snVr9lapR`}W60|b z_qg->Sit`ZqMz57c~?vv<&_Q&=C390U#|9Z<;!kny$hRqW~P1oc&Uq)`g^NmIR&Yd zS2|2V>-@{SJ|I7sYu39!?Wb)Us-QS~3`b66l6N?b)bw}&rrdM%_xwXo`}l@WXnIxx zXFn+;At}xTvJWU05nW&5Uj#lganE`e`2LGd?)OcFv_=mVrSaB<65nCx@?G7)sIxQZwG+As``Pd?kCdMHx~2@R(7Zcj%`uT z&I7ltDyU9GTG_38rU1|}K(3X0ldNIyx(l(Toj&A0s2?RUP`6gK00000NkvXXu0mjf D(CZ)9 literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like-unselected.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like-unselected.png new file mode 100644 index 0000000000000000000000000000000000000000..ba084eb8bbd190ac1fdb69a4b526989ffb858bae GIT binary patch literal 1192 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 S_;aZM00006w zyaHs!=sphI2Cf2iK%6EIjQPmTw+M|i9TUEbO)ivn=6jl99Rh^nT4XWxVhIdi| zS*WUiX!_Kks`<%6zM5hy#?pYQRwfF$S5>{bbQ)3BJ&T3hvBZ!wVJjCDo>a9EXafpZ z9t{H9MdZJkjixB^v340D0bT@7M@9ovM5j-Iw^@Ew14pB}-Uz_=mf4z-OJIwL_#!R( z#R`H9FM-czS=3k$-N5F^-T~hv_2=#~9+;&1U^#iuuMT-2s9gKCm;2prtouvZv~_*d zxBY8(aqVI+#btR|-!DZ>At$7=ot4@Zxi%NOi1&2ssI9Y>Fw#)dxXYC6%N;(Fwx=430~ zwqZJ2hG@Sr7;AVL@H#;FGxo>onP0RvWOs!Tm!I12S}82frK|nnbe{rqZcNYub1p;u zqX5{NoQwyiaNhSa;_^@XG{0zVye04fa6Rn$TR_ZTSljgh>d)Rqb#8W6T$ab-X9*0cdS}K>gXSkZBDSZCc02 zCn{n>&V)VtLmQ4B_bkZj1Lag%>)l0FYxcGV{2Q8dMxTo>`CpUUz^|&-lFCcg#D^ zh}$1ADL8sO1cUSD(Ahw#$v8g5g{-UT_XRCO!R#mZLMg2U4?DiofK;=t_{Y|VBNnF=qst1B4v%FHzHmlX_J z7n$bSfi20-I_3@isd92tvgGZ6MMN|cKtxnkzXwhwGwbCGcABm_K;X5vHuBC#TayJn z6|Pgw4ER3*&V zBK}yX&|2X4L}uN%C2v*&T(Ds+#btSkLe`7Ox%mnZ1wgBDxLTCP15-MYak#t=)tX9=8Rn?vZV3(>MSJh|Ietly=zhM+E_5$1Ew-N=IK xMC31)>YgbIbOumj#NKe!ADwX_HkIs4{s%H8YlFIW3#R}8002ovPDHLkV1f|rgFXNN literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index b2a5fd40..f021e93d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "@types/node": "^22.19.1", "@types/react": "^19.1.13", "@types/react-dom": "^19.2.3", + "@types/spritesmith": "^3.4.5", "browserslist": "^4.28.0", "browserslist-to-esbuild": "^2.1.1", "ejs-lint": "^2.0.1", @@ -71,6 +72,8 @@ "globals": "^16.5.0", "nodemon": "^3.1.11", "npm-run-all": "^4.1.5", + "spritesheet-templates": "^10.5.2", + "spritesmith": "^3.5.1", "tsup": "^8.5.1", "typescript": "^5.9.3" } @@ -3540,6 +3543,13 @@ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -3727,6 +3737,16 @@ "@types/send": "*" } }, + "node_modules/@types/spritesmith": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/@types/spritesmith/-/spritesmith-3.4.5.tgz", + "integrity": "sha512-LRyCIJ+7LW59eFnUVfF4Wwhs23MceEqFQQtV7l7hHYz/z0zeN24J5TJOE2qZjaUOsGcSZ0ISDTIhe2dvpWrnmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/vinyl": "*" + } + }, "node_modules/@types/superagent": { "version": "8.1.9", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", @@ -3755,6 +3775,17 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/vinyl": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", + "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -4735,6 +4766,13 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bin-pack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bin-pack/-/bin-pack-1.0.2.tgz", + "integrity": "sha512-aOk0SxEon5LF9cMxQFViSKb4qccG6rs7XKyMXIb1J8f8LA2acTIWnHdT0IOTe4gYBbqgjdbuTZ5f+UP+vlh4Mw==", + "dev": true, + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5273,6 +5311,13 @@ "node": ">=0.8" } }, + "node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha512-dhUqc57gSMCo6TX85FLfe51eC/s+Im2MLkAgJwfaRRexR2tA4dd3eLEW4L6efzHc2iNorrRRXITifnDLlRrhaA==", + "dev": true, + "license": "MIT" + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", @@ -5408,6 +5453,39 @@ "node": ">= 0.6" } }, + "node_modules/contentstream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/contentstream/-/contentstream-1.0.0.tgz", + "integrity": "sha512-jqWbfFZFG9tZbdej7+TzXI4kanABh3BLtTWY6NxqTK5zo6iTIeo5aq4iRVfYsLQ0y8ccQqmJR/J4NeMmEdnR2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~1.0.33-1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/contentstream/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/contentstream/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -5439,6 +5517,13 @@ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/crc": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/crc/-/crc-4.3.2.tgz", @@ -5482,6 +5567,16 @@ "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", "dev": true }, + "node_modules/cwise-compiler": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz", + "integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uniq": "^1.0.0" + } + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -5494,6 +5589,13 @@ "node": ">=0.10" } }, + "node_modules/data-uri-to-buffer": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz", + "integrity": "sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw==", + "dev": true, + "license": "MIT" + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -7017,6 +7119,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/first-chunk-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha512-ArRi5axuv66gEsyl3UuK80CzW7t56hem73YGNYxNWTGNKFJUadSb9Gu9SHijYEUi8ulQMf1bJomYNwSCPHhtTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fix-dts-default-cjs-exports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", @@ -7250,6 +7362,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-pixels": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/get-pixels/-/get-pixels-3.3.3.tgz", + "integrity": "sha512-5kyGBn90i9tSMUVHTqkgCHsoWoR+/lGbl4yC83Gefyr0HLIhgSWEx/2F/3YgsZ7UpYNuM6pDhDK7zebrUJ5nXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "0.0.3", + "jpeg-js": "^0.4.1", + "mime-types": "^2.0.1", + "ndarray": "^1.0.13", + "ndarray-pack": "^1.1.1", + "node-bitmap": "0.0.1", + "omggif": "^1.0.5", + "parse-data-uri": "^0.2.0", + "pngjs": "^3.3.3", + "request": "^2.44.0", + "through": "^2.3.4" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -7300,6 +7432,38 @@ "assert-plus": "^1.0.0" } }, + "node_modules/gif-encoder": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/gif-encoder/-/gif-encoder-0.4.3.tgz", + "integrity": "sha512-HMfSa+EIng62NbDhM63QGYoc49/m8DcZ9hhBtw+CXX9mKboSpeFVxjZ2WEWaMFZ14MUjfACK7jsrxrJffIVrCg==", + "dev": true, + "dependencies": { + "readable-stream": "~1.1.9" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/gif-encoder/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gif-encoder/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -7423,6 +7587,16 @@ "uglify-js": "^3.1.4" } }, + "node_modules/handlebars-layouts": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/handlebars-layouts/-/handlebars-layouts-3.1.4.tgz", + "integrity": "sha512-2llBmvnj8ueOfxNHdRzJOcgalzZjYVd9+WAl93kPYmlX4WGx7FTHTzNxhK+i9YKY2OSjzfehgpLiIwP/OJr6tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -7694,6 +7868,13 @@ "node": ">= 0.4" } }, + "node_modules/iota-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", + "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -7780,6 +7961,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, "node_modules/is-bun-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", @@ -8079,6 +8267,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true, + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -8122,6 +8317,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -8232,6 +8434,13 @@ "node": ">=10" } }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/js-md4": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", @@ -8269,6 +8478,15 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-content-demux": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/json-content-demux/-/json-content-demux-0.1.4.tgz", + "integrity": "sha512-3GqPH2O0+8qBMTa1YTuL+7L24YJYNDjdXfa798y9S6GetScZAY2iAOGCdFkEPZJZdafPKv8ZUnp18VCCPTs0Nw==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -8375,6 +8593,18 @@ "json-buffer": "3.0.1" } }, + "node_modules/layout": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/layout/-/layout-2.2.0.tgz", + "integrity": "sha512-+kdgg25XW11BA4cl9vF+SH01HaBipld2Nf/PlU2kSYncAbdUbDoahzrlh6yhR93N/wR2TGgcFoxebzR1LKmZUg==", + "dev": true, + "dependencies": { + "bin-pack": "~1.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8910,6 +9140,38 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/ndarray": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz", + "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iota-array": "^1.0.0", + "is-buffer": "^1.0.2" + } + }, + "node_modules/ndarray-ops": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ndarray-ops/-/ndarray-ops-1.2.2.tgz", + "integrity": "sha512-BppWAFRjMYF7N/r6Ie51q6D4fs0iiGmeXIACKY66fLpnwIui3Wc3CXiD/30mgLbDjPpSLrsqcp3Z62+IcHZsDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cwise-compiler": "^1.0.0" + } + }, + "node_modules/ndarray-pack": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ndarray-pack/-/ndarray-pack-1.2.1.tgz", + "integrity": "sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cwise-compiler": "^1.1.2", + "ndarray": "^1.0.13" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -9097,6 +9359,15 @@ "dev": true, "license": "MIT" }, + "node_modules/node-bitmap": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/node-bitmap/-/node-bitmap-0.0.1.tgz", + "integrity": "sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==", + "dev": true, + "engines": { + "node": ">=v0.6.5" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -9441,6 +9712,16 @@ "node": "*" } }, + "node_modules/obj-extend": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/obj-extend/-/obj-extend-0.1.0.tgz", + "integrity": "sha512-or9c7Ue2wWCun41DuLP3+LKEUjSZcDSxfCM4HZQSX9tcjLL/yuzTW7MmtVNs+MmN16uDRpDrFmFK/WVSm4vklg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9563,6 +9844,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "dev": true, + "license": "MIT" + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -9724,6 +10012,16 @@ "node": ">=6" } }, + "node_modules/parse-data-uri": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/parse-data-uri/-/parse-data-uri-0.2.0.tgz", + "integrity": "sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "data-uri-to-buffer": "0.0.3" + } + }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -9955,6 +10253,69 @@ "node": ">= 6" } }, + "node_modules/pixelsmith": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/pixelsmith/-/pixelsmith-2.6.0.tgz", + "integrity": "sha512-1W0C8EVxAPJwsCodw/+dfeEtdSc8JuHFipVylf51PIvh7S7Q33qmVCCzeWQp1y1sXpZ52iXGY2D/ICMyHPIULw==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "concat-stream": "~1.5.1", + "get-pixels": "~3.3.0", + "mime-types": "~2.1.7", + "ndarray": "~1.0.15", + "obj-extend": "~0.1.0", + "save-pixels": "~2.3.0", + "vinyl-file": "~1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/pixelsmith/node_modules/concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha512-H6xsIBfQ94aESBG8jGHXQ7i5AEpy5ZeVaLDOisDICiTCKpqEfr34/KmTrspKQNoLKNu9gTkovlpQcUi630AKiQ==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~2.0.0", + "typedarray": "~0.0.5" + } + }, + "node_modules/pixelsmith/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pixelsmith/node_modules/readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/pixelsmith/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pjax": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/pjax/-/pjax-0.2.8.tgz", @@ -9973,6 +10334,27 @@ "pathe": "^2.0.1" } }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pngjs-nozlib": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pngjs-nozlib/-/pngjs-nozlib-1.0.0.tgz", + "integrity": "sha512-N1PggqLp9xDqwAoKvGohmZ3m4/N9xpY0nDZivFqQLcpLHmliHnCp9BuNCsOeqHWMuEEgFjpEaq9dZq6RZyy0fA==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -10483,6 +10865,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==", + "dev": true, + "license": "MIT" + }, "node_modules/process-warning": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", @@ -10824,6 +11213,105 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha512-AFBWBy9EVRTa/LhEcG8QDP3FvpwZqmvN2QFDuJswFeaVhWnZMp8q3E6Zd90SR04PlIwfGdyVjNyLPyen/ek5CQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/request/node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11063,6 +11551,22 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/save-pixels": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/save-pixels/-/save-pixels-2.3.6.tgz", + "integrity": "sha512-/ayfEWBxt0tFpf5lxSU1S0+/TBn7EiaTZD+6GL+mwizHm3BKCBysnzT6Js7BusDUVcNVLkeJJKLZcBgdpM2leQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "contentstream": "^1.0.0", + "gif-encoder": "~0.4.1", + "jpeg-js": "^0.4.3", + "ndarray": "^1.0.18", + "ndarray-ops": "^1.2.2", + "pngjs-nozlib": "^1.0.0", + "through": "^2.3.4" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -11152,16 +11656,6 @@ "uuid": "^3.0.0" } }, - "node_modules/serialised-error/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -11449,6 +11943,108 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/spritesheet-templates": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/spritesheet-templates/-/spritesheet-templates-10.5.2.tgz", + "integrity": "sha512-dMrLgS5eHCEDWqo1c3mDM5rGdJpBNf1JAJrxTKA4qR54trNTtxqGZlH3ZppS5FHTgjKgOtEmycqE2vGSkCYiVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "handlebars": "^4.6.0", + "handlebars-layouts": "^3.1.4", + "json-content-demux": "~0.1.2", + "underscore": "~1.13.1", + "underscore.string": "~3.3.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/spritesheet-templates/node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/spritesmith": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/spritesmith/-/spritesmith-3.5.1.tgz", + "integrity": "sha512-FrzGx9cD5SkH3Kxuk24vJJLBYHLyDskUJ2t6DSWfWkCd8+yFQAfBGVE7F0IhOpKu+aq1CZ9rgXnpaa4luIQgGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "concat-stream": "~1.5.1", + "layout": "~2.2.0", + "pixelsmith": "^2.3.0", + "semver": "~5.7.2", + "through2": "~2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/spritesmith/node_modules/concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha512-H6xsIBfQ94aESBG8jGHXQ7i5AEpy5ZeVaLDOisDICiTCKpqEfr34/KmTrspKQNoLKNu9gTkovlpQcUi630AKiQ==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~2.0.0", + "typedarray": "~0.0.5" + } + }, + "node_modules/spritesmith/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/spritesmith/node_modules/readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/spritesmith/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/spritesmith/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -11724,6 +12320,33 @@ "node": ">=4" } }, + "node_modules/strip-bom-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", + "integrity": "sha512-7jfJB9YpI2Z0aH3wu10ZqitvYJaE0s5IzFuWE+0pbb4Q/armTloEUShymkDO47YSLnjAW52mlXT//hs9wXNNJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "first-chunk-stream": "^1.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom-stream/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -11938,6 +12561,61 @@ "real-require": "^0.2.0" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -11990,6 +12668,20 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -12672,6 +13364,19 @@ "fsevents": "~2.3.3" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -12881,11 +13586,32 @@ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", "dev": true }, + "node_modules/underscore.string": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", + "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "^1.1.1", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, + "node_modules/uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", + "dev": true, + "license": "MIT" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -13008,6 +13734,17 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/uvm": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.1.1.tgz", @@ -13065,6 +13802,50 @@ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, + "node_modules/vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha512-Ci3wnR2uuSAWFMSglZuB8Z2apBdtOyz8CV7dC6/U1XbltXBC+IuutUkXQISz01P+US2ouBuesSbV6zILZ6BuzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/vinyl-file": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-1.3.0.tgz", + "integrity": "sha512-i1CGRaiDs3qJ+Yc8cgtOnrZOwlhY02oDBrWSBKD9uYSsxqQG1RhNXLmR/orke0ye0sbKpVtAUHwhF2rs9A46cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "strip-bom": "^2.0.0", + "strip-bom-stream": "^1.0.0", + "vinyl": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-file/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", From 40da66ebe140e97f2ef50b7c55c7ddba85bfbff9 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Fri, 9 Jan 2026 18:44:12 +1100 Subject: [PATCH 021/155] fix: replace sprites with their actual rendered sizes --- apps/juxtaposition-ui/scripts/sprites.ts | 5 +- .../src/webfiles/ctr/css/juxt.css | 12 +- .../src/webfiles/ctr/css/sprites.css | 110 ++++++++++++++---- .../src/webfiles/ctr/images/sprites.png | Bin 7248 -> 12158 bytes .../sprites/feeling-frustrated-unselected.png | Bin 1592 -> 0 bytes .../ctr/images/sprites/feeling-frustrated.png | Bin 0 -> 1156 bytes .../sprites/feeling-frustrated_checked.png | Bin 0 -> 1157 bytes .../sprites/feeling-happy-unselected.png | Bin 1189 -> 0 bytes .../ctr/images/sprites/feeling-happy.png | Bin 0 -> 896 bytes .../images/sprites/feeling-happy_checked.png | Bin 0 -> 988 bytes .../sprites/feeling-like-unselected.png | Bin 1192 -> 0 bytes .../ctr/images/sprites/feeling-like.png | Bin 0 -> 891 bytes .../images/sprites/feeling-like_checked.png | Bin 0 -> 980 bytes .../sprites/feeling-normal-unselected.png | Bin 984 -> 0 bytes .../ctr/images/sprites/feeling-normal.png | Bin 0 -> 752 bytes .../images/sprites/feeling-normal_checked.png | Bin 0 -> 871 bytes .../sprites/feeling-puzzled-unselected.png | Bin 1256 -> 0 bytes .../ctr/images/sprites/feeling-puzzled.png | Bin 0 -> 960 bytes .../sprites/feeling-puzzled_checked.png | Bin 0 -> 1037 bytes .../sprites/feeling-suprise-unselected.png | Bin 1339 -> 0 bytes .../ctr/images/sprites/feeling-surprise.png | Bin 0 -> 974 bytes .../sprites/feeling-surprise_checked.png | Bin 0 -> 1017 bytes .../src/webfiles/ctr/images/sprites/reply.png | Bin 0 -> 539 bytes .../src/webfiles/ctr/images/sprites/tag.png | Bin 0 -> 342 bytes .../ctr/images/sprites/yeah-selected.png | Bin 0 -> 553 bytes .../ctr/images/sprites/yeah-small.png | Bin 0 -> 516 bytes .../ctr/images/sprites/yeah-unselected.png | Bin 0 -> 597 bytes 27 files changed, 99 insertions(+), 28 deletions(-) delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated_checked.png delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy_checked.png delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like_checked.png delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-normal-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-normal.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-normal_checked.png delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-puzzled-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-puzzled.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-puzzled_checked.png delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-suprise-unselected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise_checked.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/reply.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/tag.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-selected.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-small.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-unselected.png diff --git a/apps/juxtaposition-ui/scripts/sprites.ts b/apps/juxtaposition-ui/scripts/sprites.ts index 93271a07..82e1615f 100644 --- a/apps/juxtaposition-ui/scripts/sprites.ts +++ b/apps/juxtaposition-ui/scripts/sprites.ts @@ -37,7 +37,10 @@ Spritesmith.run({ }, { format: 'css', formatOpts: { - cssSelector: ({ name }: { name: string }) => '.sprite-' + name + cssSelector: ({ name }: { name: string }) => { + /* Use underscore for :checked, :hover etc. */ + return '.sprite-' + name.replaceAll('_', ':'); + } } }); diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css index 3f58f6d1..0c327021 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css @@ -1,3 +1,5 @@ +@import 'sprites.css'; + body { margin: 0; font-family: Arial, serif; @@ -215,7 +217,7 @@ menu.tab-header { background-size: 100px } -.feeling-selector .buttons input, +/* .feeling-selector .buttons input, */ .sprite { content: ""; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAdnJLH8AAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAIABJREFUeNrtnXl8VNX5/99nJgkQwipLWGRRVhcEQW2tVEErikLrAq4gWkXFatVualsBbav9tnWpVqsFUbDlp9GqEaHWnYqtKyqrhE32yE4CCcnMfX5/3DvJzGSWO5klk+T58LovJnfuufcz99zPc5/znOec46VhcRlwE9AWWI5CocgoTIR9bQFvEucsA3wurvsX4MagfY87xkC0WhSKzCIP+A2w3RFgMtseYFIc8T8WpexjUYySQqFII15OgfCDt8NApyjifzRO2b8BHq0ShSIzGJli8Qe2ExJ482faE/ACLbTqFc0dOcC303DeZYQG9QwwC7jGZflAbCAVMQGv8xu/D4wGugNdHC9jF7AD+C/wCvAWUJnOGz732tV9PZa5G6R1fc8hwh7jtZ6+ctYx/9NHWEGSQcD7gDvC9j8P7KvnOXcAjzjiClzjY2B4hGO3AFMc49AnwvezgOsBqx48PNi9DL+Jcu5I2OPcj0fTZQjmXr16tjGuDWEs+AWun/zUoNnpfECmTZtRYOXkXSBizsMwFOhhG1WzA2SVYBbm+DwvPvbYz3e4Od/UW3/bDct7ocfIeSIMBroCfmArwud4zAJvdeXLjz02o7y+nJ+ftqKg8nDOBSDnGQnmzA4Mq0TMQr/le/Hqp491xXne1JXdxOe90CDnGRgsQZzF8LnHYkFeK//LEx87trypGIDBwOoUnX8+cEkU8Y8C1gL9gHeAnhGO+30EfvHQD3gOOLGevL8GrgTeT/UNn3fN6ueAiSk6nSDcNGnOoMdTzXP69Ok523bn3WSMucvxmGLhEIZHqfT87skn79gfUfhT729HC/klyI+AVnHO9w3w224dDz82c+ZMn1vO70x/J2fL191uEiPuOGMebZmb+7uJTx4dkfPzU9e1O1x1+JdijEvO5rdH9tr+2KiZo3xqACJ39QWjGLjQsaTEMAJVQHugwuV1TwP+CXROkn8VMA2YncUGAMASmJpKT+CHt03v6Klu8RzIWQkWXSuWNX7WY79eFXK+aff183isYue5SsC6sdhvzMVzHrlrZ7xjZ/1wecc8n+c5DGclaELXYvnHT553fAjneT9c3k8sTzGSGGcDi6tN7sXXzOm/szEYAE8aDUss8QOMB54JyjloReTAnJVAE+B0px0fTfyVwP+wez2ed97wB4jeNRpogmR1HRr427yrV9+YipNNuXV6e1OV+76IdZaIkODWD2M+mHrTPafUvPlvuucUY/wfi8jgRM+HyHe9lvXelFunt4/Fec6Upe1zfZ73Bc6yiyWwQT/xeD+YO2lFDee5k1acYvk8H4vF4ETPZwnf9VrV782ZsrR9c/UA3Ig/PN5wH/DvKML9tdOOj4e+wEdE7n78CrjX8TrKIgj9TOAux3sIRzVwNvBulnoAKWsOTJjwvLdt568WAOckyWW/ZWQMgEfM60C7JH/aogM7B40rKpror/PwTBBvZf7KBZIkZwP7sRjjvBZfl6Q5s6jVoWPGTSwy/ubkARgnABhJ/NudtnU4JmJH4SOJ/48uxQ8wL4L4xRH+ccDfI4g/4Oovwu4OvTpC8C/XiWMUZL0xN/wlGU+gTedVUwQ5R0j6XzsjvG6E1wVpl/z5OLeg06rJERvyLZdPsUTOqYe3ErJZIu0sw+uW4XVLpF2y5xORcw/lr5zcnJoAAfHfFCXg913nDbs2wvcto4j/Zy6v/QPgOxHE/0PgbuKnJgfwNHBWhHhDIXB7uiujx5DWTHpqkJn01CDT/fjWGTUCN9/85xYi3E3CPnTUrZ2zpeR8BqZPmDAjL5jzn28uaQGp5Gy1s7cUcbas6c9PWJHXHAxAPPEHov3Bn2MhEfETxUv4PTCnHr9liWM4wvHT5F3Z2Bh965E1yU9n3nakyaQnUF69+3uI9ErBmy9dW+82ncyZwZwL9lR+T4ReqbNZqd0soXdFnv/Mpm4A3Io/eN911Eb/kxX/EOBY6nbjzUjiN813YhIhHjJwHjSS3p0EjYCIOTdbhRTY/H4ZG/bgnUuWkxZhbFM2AImKPxBgnB9lxGFxguIP9CaE4/+wxyMkg3uiNDXShrce3FyT9fjWA5slk0ZARIZk8dvf7hVAhoRwNtaQFMQr0vvPhHImC1OBMy3+t502dThex07XTRSnReg2fCkF9+a/TuCyW4xrpRTblh1k3jWrBWDb8oOp9ASI1zsgSGEj8Gy6hWVAFGb74FEJ59xEPIBUi/+P1L8bp3vY3xsc4SYLCztnIBhdkjSa2dscsF+zZPkmYWMiJNu9Fgnj3BQMQDrE/7MkfkP3CN2NKXspU3dgUVdohGM+4hgBEdnRCMS0LZSz1eg4N3YDEEjySUT8xwPvRRH//WHi7wU8BbyaQHNAXMxylMoZkyTO/TyO5NOQM24ERGRZIxDT8jDSy9Izkj2l2/KmZAAuIXKSTyzxR0vNvR+4M4zLIuxknPOx8/lPrcdbOpVt2XDvwo89UCUS2mNnIi5z7sc1jckIWH7rtWw3AJYlC0L173kt21st4ZwbuwH4TprED/aQ3WPCuI2phwHoS2oCLx7gW9QdpRYtqegGaoc85wEPYWcRZqMReHTuNatDch32til/S0Q2ZXE8fWN1ed47wZwrygreQmRTtqrfiGxsLy3faUoGIFIAbFwKxB8QjZt94Xg/wm+6IAX35tQI3sR/Yhzfm7p5A0dEOG5XlgwgevLZH666KrBj0SOPHLbEzMzit+mMoqIZVcE/4pZF/Q8L1sxs9Vj8yIyJRcdWNfVEoB0pED9JzmcYjruIP347Hqa7uFZgINEE4OgoxvF8xyuxX78ea16MJKiM1r2IeWzOlBU1Rs5/sOQZgyzKQvUvHNArb17EdpdseAbMoizU1sJtg4fPo5kNBx5M9FF9f0yD+AFWUHdNgR4klwl4JdQZV14GLAx7w38IvIk9ovF7Ec7zpBPQXOPENrhy1jH/k/rPcpRq5HuNp2buxqKiIn+eVF4uIquz6E26ugWVV8ycOTPi/ZpYNNGfW+3JOs651d4rZs40VnMwAH8AnnA2t9H+VONXEfb9rJ6BuNMc4Ub6ncEzx1wODE2g6fTbwB+Tnxo0W2BqFhiB6lxyVwbvePrph/b5jTVKYGUWCKnEL1VnPf30QzGnp7v65WH7fEZGIbIyC7yVEstUn3X1y8P2QfOYEORK7Id5agbd/nC8EqF9HpiI9N4EgnHXOG/0VhECjQ8kmUUZcnxWGAEjd102p9/m8N1FT/9hhyVVZ4OUNFTID6TEonpU0dwHt7r5KTcVnbzD8vjOFpGShjRYlsc36saiU7fSSOBJPLMxIWRC/DWaAnZGMAK/cpoIk4g8mq8F9iCfJdjTf7WIMCHIZUB4bu4/gJIE7ludsQWTnxo02xiuawgjYMRMnzR78B+jfV8098GtnuqqkSKSeU8AKbHwuRZ/ADcWnbrV65ORCCsboMu/RLz+RiX++hiAlVkqfoCNwEXYE3yEYwAwF9jtvM03AOuArdhTgi2IkXPwI2BxhP17sScdvQi7Tb84SjPkKqep8GhE92n2oKcybQSMmOlXzhl4T7zj5s9/uDTHVz06w0agBPwJiz+A64q/Ver1y2hBVmbQYymRnMYn/voYgCedkXz+2LOtckeGxU9QN92ZETwBglJ5u2HnHByFnegTrauxGrg2SjwggHLshKUnoxjHZx3D82XMNlQGjYBb8QcbgVy/b3Rm2tdWifFY9RZ/sBHI8ZMhzlJCtdUoxV8fA1DlBL/aAh2jbB2wJ+NoCAzBnsyjDalZPWi8Y1CoR0JSheMluAukZMAIJCr+YCNQZfnT/VYtMV6SFn+IEbDMaBFWpk37UIJPRt24oHGKP5kg4CHn4Y60NUTiQ1/nbbsUe6GRlim6N+OdoOCbwIg4x/+V2olULccDSmhOgnQagfqKP4Di+Q+XVlvWaCs9girxpFD8wUYgV0y6PIES08jFTxNYhNNgR9K/BK5I4+85EzvP/+EYawrudNr6I7HXOHi4PhdKhxFIVvzBRsAvlvNWTV2b31udevEHG4EqPKMtWJlSb8Wi0Yu/sRuA9tiJOU8Qe8berdhzA07DHrPQHztRqAd29t6pTlv/Eezpw2MZm1uAT6Jk/eG88d93goz1xpWzBz2FcG0qjECqxB9iBLDs5kAKxF/tM6OKih5Mq5BuLf5WqQ/PaEmJJ0CJxzJNQvyN2QD0BT4g+iQiFdjDir+FvdLQNcDj2HP7r3Xa69uA9dgz/8x2xD0IGIidtbg7yrmPc8qcms4fOGnOoDnJGoFUiz/YCFgwGpFVybjQPr9n1II0iz/ECJic0SKyqr6UEVPikaYj/sZqAI523rKRlmzyOR5BPycY+GE9zr8Gu/uuLzATO9Ifjs5OXOCMbDUC6RJ/sBGosnyjBWtVfZJ8fJY3Y+IPNgLG8tXLcBmREg9NS/zRVgYS7Pn5yjLMpU2EN3p4LkF3R/x9I5Rf5wQAU72gZ2+nCTEqwndlTnzg43TemHlXr74awyy3Bjvd4g/G2AnTCr3G87Zxu+6focSyfKMWFP21wYT0l7HvFFrG+3YCaxWW5JicJif+gAH4iePy0vDzJ9aZged24MGgv9+AiIs/vuSkJB9Ko6d0H/DzKDGGo1MwC3FKjEAmxR9iBDAuBCUlgtWg4g8xArgyAiU5nqYp/sCDvSSLjBFxxvqPjFSXwMVpFD+OC/4L7KnQJMLIw6PSfXMmzRk0x8XYgbszLX6AhUWP7fAjo8WSVbECftkifoCbFo7a4cE/GonNudpT3WTFHzAA/8POVss2zI3gWoe36Z8Bbs5gGu1j2CsEETZL0IZMXDzO2IG7Jz016N6GqqyFRY/tsDyMFiFSkK0Ekz3iDzECxhptrAgxAaTE5/WNun3BmU1W/MFvXQ9wK3Z32AAiL9qRCfixu+JmYfejhz/oPYE/Y08d9gL2pB0NMbnGtY7h2esYhE8yefF513w1GeRxIB+oxshdsQb2ZLo5YCwJdq1LjEeyTvzhzQGfn7cxDmdDid9rNXnxp3oGXUUGMWfKisJcT84wK8e3YvKTx27KJm5jJ0wrxG+9DeR4vGS1+OsYAcjx5zYP8SsUacP5E27ocf6EG3o0Js4PjH6rxwOj3+rRnOopJR6AiORgd80VAoF1rQ9iL9Kx0Rjjy4YfqzyVp/JMgQEQEYM9ddb3sZNhjif6sNoq7Fz9d7AX/1xijJEM3VDlqTyVZwoJ54vIrSKyTuqPtSJyi4jkK0/lqTyzl2eIpRKRKc76canCdhGZ7FhB5ak8lWeW8AwnXSgib0r68LqIdFGeylN5NjzPcNInpdhaRcM2ERmuPJWn8mw4nuGkR4tImWQOB0TkDOWpPJVn5nmGkz45w6SDyQ9XnspTeWaGp4nUVgE+B7o2UHfodmCoMeabeG0q5ak8lWdyPD0R+iWfbUDSYE/bPTdWVFN5Kk/lmTzPSDMCXYX7abDTiTHYk3xGg/JUnsozeZ61TQAnoWB9A1utcBfmaGNMRXgShfJUnsozOZ6RPICpWUQ64MJcG2G/8lSeyjN5nrUegNNOWEsGZrZJEGuBAYGcZ+WZOZ6HK3wsnP8VVYd9jJk4gPZH2AsmiyW8t2ADm9bu4/Rxfendv0PEC31dspf3Xt1Ar37tOf38vhiP7Wzu213B68+vIa9FDmMvG0iLVjnNgmc21XskD2BkIqR3lx5i786KhFns3VnB7tKEZu7qB3yb0CnBlGf94ZrnF//bzspPS1m7fDfzHlzK3l0ViCUUz1vFktc3snndPt54IfriyG+8UMLmdftY8vpGiuetQixh764K5j24lLXLd7Py01K++N/2ZsMzy55PwtesH4fLaTtfmrOClZ+WAnDiyB6ce+lAV0UXzl/N0vft5fOOHdGVH0w51u1YxPHYawAoT6CywseyD3fgqwqdCMmb6+G4kwrJL8hNCc8u3Qvs3y1wYG8lzz60lMJebVjzRe26q117Rl+CsWvPNpRusWdUX/7RDqoO+9mxqYwDeytrfM8u3QuaDc8sez7reACj3Jzh67V7a0gDfPafrZRuLY9brnRLeQ1pgBWflLJp7T631mt00Odmz/OVp1fw76I1vP3KupDtjRdKKHriy5Tx7DOwA+ddPghj7KfrwN7KEFH16teecy4ZEPUi5146kL6DOtb8veaLnSGiOmfiQPoM7NBseGbZ81lrAJxJCI53c4bqw1aEffGn5KuuqntMVZXrqfyGiIhXedqoOOiL6R2kkufQU7tz7mUD6uSLde/dlktvOoHcvOhTR+bkeph4wxCOPLp9najTmAkDGP7dHs2OZzY8n5E8gL5En4QgBH0HdaDwyFp3qnf/DvTo0zZuuR592tKrX+0N7tarDX1jW9VgtHA4Kk/g/CsHccK3uzH4xC4h24mn9bDdwRTyFEvYvG5/nZnQy/Ydpnx//EWgD5ZVcWBfZR33d/O6/ViWNDueWfJ8hq0jITISWOz2LL5qi5JluzAew4DjO+HxumuAWH5hzZe7EBH6H9+JnNyEViUb6Zh35ZkaxOUZCKQt/2hHaJ+Ro4e2HVpy5a3D6NCpVeRA1a4Knn1oaa07HYbjTi5k/KTBNVH3ps4zW55PY8z74R5Am0TOkJPrYfCJXRg0tHNCD6vHaxg0rDODT+ySKGkcjsqTlC7DFpPn8o9LQ0TVZ2AHxkwYUONlH9hbyWt/Xx21/Gt/Xx3Slh4zcUBIW3r5RztY/nFps+GZRc9nk1keXJEh9BnYgUtuPIERp/e0o9WJ2CljB9pGnN6TS248IV5ArVnwzCYk3ARoIJzmGCvlmSGeIsLSJduoPuxnxOk98ebUvivWr9rDlvX7Gfqd7rRt3yJi+QP7DvP5km30PKodRw2ujbL7fRafvLeF3BZehn2ne030vqnzzJZ6N8YsCTcA/bGXxM5m9HNusPJUnsozCZ7GmHXhBiAHe+7xvCwlfRh7jnTjlufObQdZ8PdVFLTNY/xVx9CiZU5iF6z0UfzMSsoPVHH+FYPp3L11s+bZWOo9gI1f7eXNF0voemQbxl42sMYrOLCnkuJ5qwAYP2kwbTu2rHnbL5z/FaWbyzjrov6Juv+Niqcxxh9pLMDHwAg3Z9m3u4Ltm8rqrpHrEt4cD116tK7J2XaBj4wxpyTCs3juSpZ9aAeGjj+lkPGTj0mI4yvPrKwJLCVQvsnybCDUi+fu0kPM+b9POFxp50QMO607Yy8bRHWVn2f+9GlN1l/XngVc9ZPh5OZ5Q7LrWrTM4eqfj+CIrvlNlicRUoHfcUN8d+khZt33Eb7q5Bbjzcn1cO2dJ7sl/3bQZ1c8u/VqWyOsZR/uoGPnfE47t48rbv9ZuCEkqtytV1u3P6vJ8mwgJMzzcKWPoie+rBEVwNL3t9HtyLZ8vXZvjajAzqp77R+r6d2vQ0h2XeAcV/98hFuPrDHyJLwX4BW3VitZ8YPdB5rAYIbi4JeemwIjvtsjJLPrvQXreeXplRwqryZWMsjLc1aw+LUNIWmkI77bo9nzbCAkxFNEKH5mZcTnatH/+4oVEbryVnxcyqL/91XE57z4mZWISFPlGXE4cAlwNHGSEF5/fg3bNx1Iqma79WrLmIkD3PR/lgADw4ZbxuUJcKi8mrkPfBpyo3PzvAw+sQt9B3WoaYLs21XB+tV7WL10Z0iq5RFd85l8+3A3g2uaBU9cZNMBtG6TkpBCwjyX/Gsj7766vubvk87oyaql39TJBAy0mzd+tTdkf0G7PAYP68LH726p2XfGuKP4zjl9mhzPOk0AY4yIyJ+Bh4mThHDuZQMz+RZ4OJi0W54A+QW5TL59OC/OWsamkn01udRf/m87X0Yf3glA7wEduOja42jVOld5usAbL5Tw0TubwcApo47krIv6Z7zeP/tP7WreA4Z04nsX9+fYEV2Z99BS/D7ba213REsu/OFxAMz+/cfs311ZE5e6+Lrj6d6nLfv3VLLmy10154wjrEbJM1oi0CxgRxa5gFuB2RH2u+aZX5DLlT8expiJAyhoG//NVNAujzETB3DFLUMTEVWz4RkJ2zeV2eJ38uc/fHszOzaXZbzeBw7tDEDhkW0Yf9UxGGPo0bcdYy8biPEYWrXOZcLUIbRqnRvy2XgMYy8bSI++7TDGMP6qY2ry9APnbKI8604LLiKTgLlZYgAuN8bMj9KOSpinr9pi7fLdrF2xi9It5SEua9eeBfQ7rhP9jzsiJJFEecbHhtV7+Mcjn4de6OahIUNsM8XzwL7DtGmbVydv/1B5Nbl5njqjAqur/FRXWXWaT2IJZQeqoiYPNRWekQyAAf4FnN3A4l9ojDkvzrTLyjMLePqqLeb83yd8s83ptupRwNU/H1EfA6X3M8M8oy0M0hVY6kwm2FCu/zBjzM440VTlmSU8qw77aya4OGZ4V/JaePV+NhKe0cgPd5YXyjT2i8gw5ak8lWfmeEYjf0aGye8XkdOVp/JUnpnnGcuCbcsA6S3JWCzlqTyVZ/I8o5HvIiL/SiPphSLSWXkqT+XZ8DyjRjVF5IoUW7EtInKZ8lSeyjO7eMb6Aa1E5GYRKUmC8BoRuUlEWipP5ak8G5anSeJHnIq9EMJoYCjRx0EfBr7AHo1UbIz5byb7QpSn8lSeaTAAYT/CC/Rx+jwDy6iUY69MujF8EoKGgvJUnsqTiPMBJIV3Z2Da5+CxBEOAoheDH3MgRUZGeSpP5Zl61OukgpjP7uE04+f7GM7AXhElmutSBXwpwjtiKB4+gyUGI5m4ocpTeSrPFBqAT6ZLvjFM9cDNUv8lkNcBf7aEWSNmmkPpuLHKU3kqzxQaAEHM5zO4SuB+oGuK7sMO4BfDZjAvVRZXeSpP5ZliA/DRDCnMgWeBM9PjBvHvHJh0wgzzTTLnUZ7KU3kmzjOmAVh6j5wkFq+m0FpFw3bxMG743ebT+hRWnspTedaPZ9RB259Ol9Fi8XYGSAN0w+KdpdPljEQLKk/lqTzrzzOiB/D5DDnZgreo7YskM1FRyvAwyq0FU57KU3kmx9NEaat8niGLFdGN8cLQeG0Z5ak8lWfyPD3hUUonUNFQpAG6+WGuICZWNFV5Kk/lmRzPOgbg8xlcRZqilAlizGczuCK6a6U8lafyTJZnSBPgk+mS7zGsb2CrFeLCVLbl6FNvNxWEJVEoT+WpPJPjWccDMIapWUQaoFvLMq6tY7GUp/JUnknzDDEAghgP3Ey2QbgluA2jPDPH09sC+lwMR18Oee1DHnC6j4b+V0NBn+iXKuhjH9N9tF0mgLz29jn7XGxfo7nwzKZ6r2MAls5gZCI5yS07QYt6rPvQoqNdNgH0+3wG3w78oTyThmueHYdCh2OhbX9bIC062ALp9QPoOhIKekHPMdEv1HOMfUzXkXYZY+xz9L/aPmeHY+1rNBeedTwFD7QfDO0GJjgix9hl2g+2z1Gfeid8OLDAOLcc+lxs3xSAXZ/A5tfclTvyfOg03P68dzlsfNHtGGnGAx8oT+eN1xI6DgFP2Ipglg/2LgPfodTwrPzGPgADeW2h/xQ4uA3aD6o9pqI0+nUqSqFVoSPSIeDNg1bd7XMFOqsrv2k+PEM07IWjr4Q2jmey/yvY8BzEW+TXGOh7KbQbYP9dtgHW/R3En9jzWccDMDDKzcNa0Kf2YQXoNAJauWjttCqsFRVAh+OgoLdrizc6yPg1e559LoSe50L3s0K3nufAUZemjmfZBtj0qiMuILdtqKjKv45trDYvgLLaRXBpNyhUVJsX2ddoLjzDvZY2Qc2SdgOh/THxy7U/tlb8AG36QscTEn8+QzyAd6ZLDvY45LjwRFiH0pOXvnIOhjz/vHg7r8AoT/C2iu0dpJLn7qX2W+fIcaH7D2213zxWdfSylg/Wz4d+k6B1rzDR/Qt2fdz8eNbUcYRpeIyLNV5NjrtnKBbPiRNDZxXytM+hb4xJCEKt7Xo4FLRidflGOLQlfrlDW2xLXPP3tlCrG69JfvQK+ipPG5tegd2fw76VoduuT2HjP1PL05i6ogi8ZXPbxL9QTmvIbRfB8+nlqv3a5HgGsHc5VO4KaoZsh/0r45fbvwoqgtYdrtxpnyuR57POvfv0HhlpLBa7PYsnx3ZZxLLbLmK59EA8djnjsctZPvdxDwtGGg9GeaYGbngGAmkdh0T+vuoArH0aDu+N8rR1gH5TgtzpMOz5Eja9HLvd25R41jFObRzOYnswvgp35XJawRHD7PbGni+huiyxeh8xw7wf8vx5/LRJ6OHxwd4VsG9VYg+rWHaZvSsSExWAx9BGeaYObnh2GBIqqrINsGVRbVs7ry30Gh+9fK/xoW3pLWFt6Y5D7Gs0F57hqC6D0iVQ+oF78YN9bOkHdtlExB/g6Xo4sEIRLKr1/4CdHzkBtUTmnRG7zM6P7HPEDKg1E57ZhBzLS5mxspukMRywvHiUZ+Z47vnSbp548mxRBLqadn0KVfug9ZGw67Po5Te+BJ1OhIOb4cC6Wm9n3d+h88lgVdnXaC48s6Xe6+xbeq/0Fz9rspm45aWfFzzKU3kqz+R4jvi1WRfiAezzsaGdocptBLMBcHj9QDZ2XoFxy7NlF+g9HqrL4euXwH84sQt6W0DvCyC3AL4ujp0I0hx4NpZ6J6h/vMcYO7q+aUGtV5DXDnr/wP789ctQtb82MafX+dCqG2x9PWH3v1HxrBMDGDXT+IAv3Z4lr72dtFDfrd3A0JxtF/hi4kTjT4Rn11Mhv4d9rZ7nJv7k9Rxrl83vYZ+rufNsINSLZ8tO0PcSO6Gq49Da++rJtROlCvrY21GX1vah9zzXPrZVV7tsgmngjYpn5FRg4R1jGBHvDC2OgEE3RE5kSDTyvfqvcHi3q8PfDkpndMXz0LbaDKmOJ8DhPbDDZQdN4emhUeVD21z/rCbLs4GQME9vC1sYwYN3Og2337AFvWtTfsH+3GucnfcRnP0ZOMeaWa49skbHs04vgMfLK26tlicFi4l5chKwXh6Kaz665LnrYyjfVPt3t1F2Cm1OfuxkkD5OsCyKAAASP0lEQVQXQbczCEkjdZEJ1uR5NggS5Wns5lCk5+rI86BDhBy9Dsfb30V6zntf4HKQTiPkWWdCEEHM0hmUAEfHS5LpORbyuydXt4e2wZaFrvq9S4bNYGBgwQO3PMEW0YBrbK+lxvOotrPmDqyzI8SBRJA2R9nNk+C0yspdUDIn/uCa5sLTTTYdgO9gSuSfMM/CkdAtKNt950f2vcotqNtVGGh/B6O63L7nnU+u3bf9bdjxn6bHs04TwGDkM+TPwMPESZLZvCBzLwExPBxM2i1PsAWx5inoO7F2QI8n13a14w2gKN8IG553n6DRHHjGjEWcA51PsfvSv/nQDlBlut47BTne+7+CLf+yR0f2n2IHz8A2phuL7M8Dp9bGosRvj8Y7uNUOwLUbWHvOWMJqrDwjJgJZwizsZYayBVsPWMyuEz9IgKfvEJQ8Y2d3VZfHP7663PZMSuYmlJ3VbHhGQn43R/yOP9nlW/a+TNf7vtX2/xU77B4VBA5ucaLrAv4KWP+cfb98zmd/hf3dpgX2sYhdNpBvHzhnU+QZcVrwz2bKJIS5WSF/w+UnTjfzI31VH56eHHuCh7YDIL8w1GU9tB0OlMD+Ne7GVivPWrQ5yh5JF4y18xIaRJUynrltwVdWN28/J99uVoWPCvTk2lt488kYyGkD1QeaNs86BkAQ89kM/mXg7AaW/8ITZ5jzYi6rrDyzgqcnBwZcB6261E6w8dXf6mGg9H5mnGedsQAGI3m5TAa2N6TrTx5TYr8klGe28LR8sGa2PSnHplfteEY9xa/3M8M8Iw4GOv6XplQ8jBMoawDSBzwexp14l9kZ70DlmT08rSrY/Zm9WVV6PxsTz4ijAYffbT71COMzTP4AHsYPvdssdVtAeSpP5Zkcz5gpBJ/eI8ONvaxxtzST3urxMC6Rm6s8lafyTJ6nJ54F88JQ4PU0kl5EHsPqe3OVp/JUnvXnadzNlSDm85lcLsIfUmjFtmL4WawuChJe/0B5Kk/lmXIDEMAHD0irlmVci3AL0K+ehEvE8PABi9mjZprKdJhC5ak8lWcaDEAwls6QUwXGAaOx3Zto46APA18Ab+Oh+MS7zX8zGBBRnspTeabDAATj+efFe9RX9MFPNyMUOPnH5XjZvn4gGyONQ24IKE/lqTwVCoVCoVAoFAqFQqFQKBQK3C6HLmeKyJ0i0lbvhkKRPpwJ3Am0zSLxDxSRKrHxjFaRQpEeDASq7DlgeCaLDECR1MIvIkO0qhSK1KPIEb8AfmBIFoh/hIhYEopirarGCaO3IGsxAvgorI5eBcY3sAH4N/A9alacrhlQdpoxZolWW3TccNtveojfuxooENjW2tui34MP3l5Bg86+rshW/C5I/IEJ1McB32lA8Z8eJP6twC+Cvr5fqyzO29byDAc7w89A90qroj8NvvyCIhuRrUILvvZ07OmvA4tNniYiY7XqoqPK7/f4LT+BzVfd8PpTA5CdiCk0YGwDvP0vBL7l/LkGeMYYUw3MCOYtIp4EehLuE5E3RGSJiMwXkUkiktdka9UPlt+q2aqpRg2AIhx1hAbUEVoCdTcQuA94A1gCzAcmkcAqtiLiBe4N2vVLY4zP+fwP4HPn8/HAJXHOZURkJrACuAM4CzgVuBSYC3whIsc0Sf37fViWVbNlgf7VAGQZ6ggNqJfQnPhBTKEBboU2OejYT4EXa+emNxZwd9Cxv43zFj/POd7r9G4sBxYDe53vBwFviUjXpla5PvxYVu0GagAULoWGHQgMEVqct7groQFd47yx84BfB+2605jQZaaMMa863gVAX+CaGKdcC+wDVgMjjDHHG2NOx54hJ9D0KQz7rU2nCRDkAVRRpQZAUYM6QnOES1g3YL2E5ngN9RHaNOdaAIuNMW9EOe6OoM93i0jENY6NMauBLsAxxpjPgvYfNsbcCbzj7Pp+02sC+OvVBJg6dXr+1Ft+N//6W+5bcf2P779SDUDTRIjQnDZ7XKEB0RYTrxEa8BmhM8u4EpqIFIRd746o7Q1j3gcWOX92A26OcWw10ENEfuFkFb4mIo+LyDjgQ+ew7k7soQk5ALU9AH7L7zoI6GnV4gqDuRQ4BpG5N/z4d9epAWhacC00wLXQsN8xPbC7EYuA14DHsfMJaoTmNBMi4SdBTYSXjIk7DdUdQTkLd4hIxyiG5afYvRr3Axdj92rcABQ7zaBADKNpJar5wjwAt8XE+sYSC2czfkueuO7m1BgBNQDZgRChAQkJDegY5bh6C80Jwv0yaNdJIrIu1uZwDyxh2R54NIr4/xAUv9gCfOl4JgGD1CRhiXUo2AD4q6tbuyk368+/ekV8/oeCuhCN5fc/cc20e69TA9D4UUdojmhjbXGF5og/GaHdC+QG/d0TOMrFVhBUZoKI9AgSf2FQL8c24AxjzJHGmBOc5spfadJDqP0bgnsBxO8+cWrW43ff5rf89weVN2L5n5hy/Yxp6FiARo0ngeuS7mGCPthZg2AH9zYALR2hXQ6853zXFvi94wkEkBvU3Ygj2hKgleNp/As4mACf4Y4xAPiLMeZHznl/CMxy9n/PGPNmBA9hs2NsAHKD8g2aBL5/yc1fAQNq4jHChFeef+RVt+UnT51+H8gdocsNyI/m/u3ex9QAND6kVGjAj5zPIUID3oxQNkRoYQZgNrU9DA8bY25NMGuwG3YvRL4ThxhsjFknIr8K8gAKjDEHI5T9L7WJUHlOwLDJYNyEaVMQMyds938RWQnuZv3Nycn9tsdrjg+VsXn4xX/86VY1AI0LIUIDEq3AOkJzmgghQotiVEKE5pRHRAZgJw/lAOVAP2NMaT1Sh38P/Nz581ljzCQRmQIEHv7xTv5AcBmP48UUNlUPADBjL7xxPsglqT5xXm7umJefe+TfGgNoHBgQFIgrx07XTRTbg9r/udSmC28JOmZ0lHrvE+JGEjIKMcf5/Kf6iN/BfcAe5/PlIjIUO4+h3Nk3R0QuCHT1iUh37GzHwiZe71LayT9JsB515lUgVZsf6aceQOPBC8BFzueZYbn+iaC989bv6DQjhjvu/Ubn7b/biTEUY08q0h14gNBU4lzAJyIDgVXOc7ELONoYcyCJAUR3YWcsAsw3xlwuIlOBJ4IOK8NOWOoZ4Xlsih5ADcZccP0JXsw1At8GOcK9ak2+sYPHNffL680pebXo0UFBvUNqAMjuqb5ChAYcSOJ8IUJzgn4JCc0xAAOAlU5ewK3GmIeTHEHYGjshqScwzxgzOSgY+ACR5zlchp21+JUxZpA+KqG44LLbzjGGl7ADvAG82Kng4GVPPvlktXoAjcf9rxGa0/5PBiFCC2pauBIa9riAgGhPcWILr4Tn/NfTCPQGvoudSFQetL8DMAE7TTkf+BpYaIxZ4jQXvjbG7NVHJVj8t5xjCS+ZYPELL3ZpV1kv8asBaFjUCC1Czn99UCO0oHY2QEShYY8pGOr8rULLcoyfMO0c8IS8+Q282LVDVb3FrwZAoWgM4r/oxiF+e37IFrXKlRcO7up62bvvzkwqRpKjt1ehIMuHEFijxASJH16o2FuYtPhBuwEVikZgAPzFwDeOyz4/VeLXJkDTQTvs4cRgZwQeIDPzBIZcN5kuQ0VsnH32pNa5BQVdX/vn4+v1biiC0QU7oh9YQGSZsy/d4u8iIsuCFgdZJiJdtDoUioYR/7Kwz10yJP5lYZ/VCCgUGUBn7OG9gp1UVBhhX7c0iL+ziHzpCH6ViBRG2NdNq0ehSK/4yx2hrwnLn0+bEXCEXu4IfY0zvj+aYVAjoFCk+c0fEHmXON5BtxS/+QMi7xLHO1AjoFCkqc2/CjudOJrIw+MD7VLU5l8lIiujiTxCfKCdVptCkfqAX5ewN/0O7PkAopW5M0UBvy5hb/odIjI4Rpk7teoUitSLP4DBQIXzXWmYJxBc7o5UiD/ou8EiUuF8VxrsCYSVu0OrT6FIj/iDv6sIaw4k1QSII/7g7yqCmwPaBFAoSGtXX7Tvjg36e01YfKAw2a6+GN8dG/T3mrD4QKFWYXZDU4GzW/xvYY/ZXw2Mctr5sb7rgj3776Cg1YGCy7kSf/i5jTE7Yn3neAd1rhsop4iMCRMmeA9Zna4UkckgJ4JpBeZrwVoI/j8t+uesLSHpwN+fdmSOx/oJhrFAbwwHjcVnYnimtXf3P4qKivxqAJqv+APfvRcUFzgq3eIP+i7kuir+2Dh3ws2dPZbvJeA7UQ45JMjVC1/86/MA51847RIx8hTRl4L7j+XJuWhR0SM71QA0X/FH+y7d4o/4nSIyzpgypWWbg60XI5wU51ALuN75/ATxR+9+VFZw8PR3n366Ep0PQMWv4s9OtDnU+sfGmJNcvH49QfM6uhm6f3Lbg21uxl4RCp0PQMWv4s9CeIz3BmM8uNw8zubueI+5UZsAjRPbsLvvVgOn40wAEUfgXZzvjgOWA2cGlXNrAEKua4z5xoX461w3UE5BnMDfbT18XmtLOq9hckz3fz770HZtAjQuFCboFSQt/mjXVfGnD76W/g7G703rNbzi6YC9aIw2AWh8XbKDsCPqx8Vx+990jlmNvf7fN6m4rogcF8ftD7muij8xtPTl7vAYQxo38XkrS7UJ0PggYfP1H8aeBTb87Z5Umz/C2z7mdd00CRQJNgMm/fQLYEianqLPi5794zANAjZenOm4by0cMV6eLvHHu66KP02unvE8YIwhHRvG84AGARu3B2CwVw56l9rg3Ghn+e6Uiz/gARhjjLM0WNzrqviTtwGXTv7FCxguTPED9OJzz/x+QiILzWgQMDuxBjghSHjvYS/sOSjJgF+cN5NZIyIxr6tt/tRodX+7Q5e3L2v9IobzUlR7b/jLKyeR4CpT6gFkDzZjr+2XDrwPjIziAaT1usaYkVq1UbsEW+W1aVkM5qwk7cmbVWWV44uKHqxAFwZptJgKbGlG1232KCp6sKKqrHI8HvNmEu3+xZ7q6h/UR/zqASgU2eAJ3HZbq5blBcXAWYnFb8xic/jQ2Hnz/nhQhwMrFI3cCOQfLEikObDYqkhO/GoAFIqsMgIPtMqvOFBsJK4nsNh/qPXYefN+dhCdEEShaFpGoE1leTHIWVH6+hZXF+SPnffH5MWvBkChyFIj0O7wwWKpGxNYXJXfMmXiB+0FUCiyDkUP3l7RpkXr8R6PedPjMXg8BuMxKRe/egAKRRbjttseaHXQV1mMSF5Fy7yUi1+hUDQCI3DbbQ+00juhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVA0EcSaFnw0cBcwEPBmIXcLWAf8AVigValQpA7nAD5AGsFmARO1yhSK1OHjRiL+wLZWq0yhSF0ToBrIcT7/F/g8C7kfA5xOYMlEaOHwVigUSSL47frTLOV4fRjPPK02hQJdHFShUKgBUCgUagAUCoUaAIVCoQZAoVDUIicLOJyG3Z23AijGTuxRKBTNwABMAx6lNh9hFnCdVotC0TyaAHeHJSNdC3TTalEomocBaOVyn0KhyGID0B44GxgJ5CZQbnbY3+8C67VaFIrGEwOYBPwFaOP8vR64CHfjB36GPaT3u8Aq4E9aJQoFjWYswPXYUfvw0Xl7gBHoWACFosk2Aa4HHifyiMIOwL+B4XqLFYqmZwCmRhD/74G3w4zAG2oEFIqm1QSYGsHtv9f5Lh94K0JzIFEjcATQVpsACkV2GYBY4ieOEXATExgCfETtVF+LgB5qABSKhjcAkQJ+v4lSPt9pDgQfuzuOETjJMRThAcUNQG81AApFwxmARMRPPZoDI4C9Meb8Wx/BCKgBUCgyYAA+cuH2J2MEIon/LWB5HCOgBkChyIABkHqK301M4KQI4n/TKdMVWBajOaAGQKHIsAH4TT3PFy0msCeC+IPHAsQyAmoAFIoMGoDfJHnOSJ5ALPEH0CVKc+B3agAUiswYgHtTdN587OSg8PP/2/kuGiIZAUsNgEKRfgMwK8XnzgN+AXwALAF+jrsRhF0iNAfUACgUzWhhkK4xjIAaAIWCpj0paClwJvb8gQqFohnOCvwN9tLlagQUimY6Lfg3wCNafQqFrgugUChI35RgvYFvZyH3o7T6FIr04GtipwNn27ZXvRmFInVNgCcb2e+Yja4opFCkDF7gQaA6y9/8fuAJNAdAoagXTJzvOwB9HIOQbbCATcAurUaFQqFQKBQKhUKhUCgUCoVCoVAoIuD/Aw+o3QHaP+QwAAAAAElFTkSuQmCC) @@ -517,14 +519,14 @@ header svg { height: 28px; border: 0; text-indent: -3000px; - background-size: 172px; - background-position: 0 -29px; + /* background-size: 172px; + background-position: 0 -29px; */ margin: -1px -2px -2px } -.feeling-selector .buttons input:checked { +/* .feeling-selector .buttons input:checked { background-position-y: -58px !important -} +} */ .feeling-selector .icon { float: left; diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css index cd1882bf..0dea9d06 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css @@ -16,39 +16,105 @@ To change `display` (e.g. `display: inline-block;`), we suggest using a common C // HTML */ -.sprite-feeling-frustrated-unselected { +.sprite-feeling-frustrated { background-image: url(/images/sprites.png); background-position: 0px 0px; - width: 42px; - height: 42px; + width: 28px; + height: 28px; } -.sprite-feeling-happy-unselected { +.sprite-feeling-frustrated:checked { background-image: url(/images/sprites.png); - background-position: -42px 0px; - width: 42px; - height: 42px; + background-position: -28px 0px; + width: 28px; + height: 28px; } -.sprite-feeling-like-unselected { +.sprite-feeling-happy { background-image: url(/images/sprites.png); - background-position: 0px -42px; - width: 42px; - height: 42px; + background-position: 0px -28px; + width: 28px; + height: 28px; } -.sprite-feeling-normal-unselected { +.sprite-feeling-happy:checked { background-image: url(/images/sprites.png); - background-position: -42px -42px; - width: 42px; - height: 42px; + background-position: -28px -28px; + width: 28px; + height: 28px; } -.sprite-feeling-puzzled-unselected { +.sprite-feeling-like { + background-image: url(/images/sprites.png); + background-position: -56px 0px; + width: 28px; + height: 28px; +} +.sprite-feeling-like:checked { + background-image: url(/images/sprites.png); + background-position: -56px -28px; + width: 28px; + height: 28px; +} +.sprite-feeling-normal { + background-image: url(/images/sprites.png); + background-position: 0px -56px; + width: 28px; + height: 28px; +} +.sprite-feeling-normal:checked { + background-image: url(/images/sprites.png); + background-position: -28px -56px; + width: 28px; + height: 28px; +} +.sprite-feeling-puzzled { + background-image: url(/images/sprites.png); + background-position: -56px -56px; + width: 28px; + height: 28px; +} +.sprite-feeling-puzzled:checked { background-image: url(/images/sprites.png); background-position: -84px 0px; - width: 42px; - height: 42px; + width: 28px; + height: 28px; +} +.sprite-feeling-surprise { + background-image: url(/images/sprites.png); + background-position: -84px -28px; + width: 28px; + height: 28px; +} +.sprite-feeling-surprise:checked { + background-image: url(/images/sprites.png); + background-position: -84px -56px; + width: 28px; + height: 28px; +} +.sprite-reply { + background-image: url(/images/sprites.png); + background-position: -40px -84px; + width: 16px; + height: 16px; +} +.sprite-tag { + background-image: url(/images/sprites.png); + background-position: -72px -84px; + width: 12px; + height: 12px; +} +.sprite-yeah-selected { + background-image: url(/images/sprites.png); + background-position: 0px -84px; + width: 20px; + height: 19px; +} +.sprite-yeah-small { + background-image: url(/images/sprites.png); + background-position: -56px -84px; + width: 16px; + height: 16px; } -.sprite-feeling-suprise-unselected { +.sprite-yeah-unselected { background-image: url(/images/sprites.png); - background-position: -84px -42px; - width: 42px; - height: 42px; + background-position: -20px -84px; + width: 20px; + height: 19px; } diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png index 463f97761ccb01b14148542ce51558990efa0d27..ae7c73bc4415c21297903c015994c2ba3a14869c 100644 GIT binary patch literal 12158 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 DR_F>G diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated-unselected.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated-unselected.png deleted file mode 100644 index f0edc500e2eb2a25d1a6753c45a16984a365f802..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1592 zcmV-82FLk{P)icHj#j56A{$ zLJW5TM&Kq;22?7ggz1Ec%n^~(BGN2xk0ufMUPR_h2SFtw--t+;OdUE!rKw`UN;6%#SB(G(QviH(a6X5Iv3DW$~g(H+4^0@PM_ z^5lV;rn(-A&(vV)9z@(iS#dp;h8MIp_j0|oF{E3rmp0Pc+)Jh51!cwch+D994^n)l zhNik6o;)y9TiqGTd=>B!fOi^uLXAvHo#*kpK%d3V#UE=(Ssst6_LXl;a$0;yqRDCT znA`dRsJq*R)8V4)rHwv|-M6Mmsq?~ZfxSSLcj@l~A+NAURUK56JVj(|bhdm|B8N_` zXI4y9P}|`lH&=c!^1R`7P!<2|fS!#XFAYcZo>FRy(t_TBF!3Y+HI?mDl$boO)sVVk zK3|>Q5R#-n*^-|h(c0V_Kr(-S7MpU@A{{CRFjuEV=3z;STWD>z29N}>^*ZRcIwKz6 z0h^P)Ui+w*LTj@Xw>x4C9SdaWwDch?$+F^l9{<}lMtyBo2N%zJ4p=Byx(B)N!$Yh+ z_Az}=9+>UznV%RN)V{CR!K2C!(pDz0a`i%AyKC6Z z{abC!ij88!wp5e~JL~OAL^1&r?>APBTb}&!;;Drjzc-S+ES`cQz0b>iukDl-*Lzg@ z$ZF;R#bn{iIU_?(7XXH<^P7nftyN07yc_Q3xTap|pe#%Q4eEs3x5dnYsM^D>;+Y$08O3y#WWb7V$B9#hyy@u<{ zTDO?O6WNgt5x`cZ)E%!!tAOTkqpJ)p0VFr)r7KBoxh_q{ zZmJ9|kw`WI)xN-4s|EcO&f?)e?E&7{RhUWP@$3M(N?0_TeaF_a@7P)r7R~mxM@8!> zJf6+2!c4N(Ee`t55IF0tQ~3-2uRvEQ3w_QK&t0}Gp1nucpp;^1Mgj#zdX!EPH+L5M zPpl(u?yP`krBf6X=~6*dyimMUjeO2AO%3kEZ~U4Af}J)l8rr*r1ep6)vwwDP`7D z6Wa`s>;v|UCK;*zl&95#-W9AOlKZw5y^UH)aJtbZA{!=KBTW*KKc|sog@`Pf%F_7Z zDfVt#SP7aW;W!cbPDC0fLC_>3r$l7-wEw=*NN?5DVJDCiCZ2TwwOSNZrj)w#&i*q6 qAsq#zYuf8Ad-VJL5OYoTBmV>Sdvt$L3r9%+0000bdsxC7iW48!JGASoj0GfBOY?no*r zh?1n+e$JKu?Z-Nt-2^vn8`6eIY5Ur$Vt$RI3{Y^y2W>RQFE(>OP>OVgpQ!+2mbsdQ+1V4tBN> zI2k)zU~nQh!hxO%A8&K6H^2-TgRrk)gg(dJbRWWY5(A z;mMw>^c=p#P~RwyUF1Y}Ki!8ek{nJ~r+z5O3Ij`k<&}~8hA6Rki0fA-ICA?>@=g)g z_E`Dm!T@1oOPt|>6sLX|Bx*%SjXBJ3ic#MXtxjDIw3S1T>re+i2FRjhibnRf=4u zK(SB?5Ecq0vYEVJdvLQ}&06~$Z6**nr;%iapT8Nv$$GS{N-)1U#_+%-=YF~tSmoUD zYuxBdF~2EB+o}Yv?Q!DBRYsDTTBMEPnO>c_i8*wvUC8ELE4Z7?aOT(`p-Axlh9V{} zFRQ0(_X?U9&*kj#L2g}7Kk_--SM#=}U3tX^;$UYB%DY}5FYkJ?vxVugS1c)$c4xYe zd&9n-m8UYXNJ2e~nj;^xpKFDI6zNzG&OE>x%VznE6k3)9jxEhO1Ugd(_qV< zcDC$k$21MfWwCx+D?9df5VgWw?!8NDyr$Fm-Y^Vjs!P8GJ_lA!MXh**mFpT=ytIL( z?F(pG(tzuD#Oorgc%vDB^mZ3JS7hymSBb{LbiTigs5WBZt>-8N@2#TtiJQO#r_9( W{jgg%A+`(v0000z(m zOti*?otj`1?ZW5+V>Hp$DM1%5SjYxVOcCM^Ok@QKbVDK=trajXXs0O!Crw*xJ7cHq z%-rMRzP2;-rk~*?Z}Hwa_x$hqc<1~T8<5{lNkkQhqu7C>1!23Vngo&}q(m`xD52)o z^QG2O{`!RkwW1Sv3)llZ0q8O_1B?U1z(phU9qLuNO#$9YNWg`o0%w4IY)<|JP6pxn zfrK*a0{(tV!eaOmcn_#%Ymx!J70u_*^{Dw(0S6NjHt;p@{(9-&fCz9_G@lfM>f$sS z`05dXEd}#|GMv7X5bXnW;VAI#BLXW1?aINCl9YPgIJkud*{Ix|-$vW>Csj@Zc&)#$ z_+H@{1R^Wda2p_4M$R)@OmjK_?aLf^CR`#b}jxIL?l85L{ zc&ZBmE(EGzcGw@`!ZbviA=VCs7on;KavAHREXqAbh-(Fn-UL~3G6f@-?ZK)V2!voJ z2@kLO0Uut4nIUVl1UB-M^-(4bqS>KQv{V>Z1Rn$K6un14hx~v#1WwU-&_N4aL9`ND zH9~#4CK7}AD>jvxQOKns(g^Wa{a)g)L3NY0$&A`an~zx^6=b`!S|8`LFq?$YOWp-u z2b1^PnY?YT`#;0n1K4%kUT(F&QVsMDUzK zW-0=9ItWQ`#gx=>=|HF+c6C5@!ZwH#wb6o&$}B?TVF=g5=oMI)SOpA>K++K*r4-}X zZY{mN1Mv>1jzOde7H7b%gPK;W-`NiHQ&9ITC~a+rf2f>#U|b0)ClKJiF$|XjDhFa6 z5RKV-213yElC6XB)@$tnJ19h~O*CfDj9pnNMUf1l8Pf-QR2I1C>(n?r{xlR8VeA@A z4q7lVkh*CfG4|W!ptUJ1+B1=OgS(CWRXTu!D%`~E_W!r_UR@G_UJ$Y2MDpbhlb{TR#Y?)DdQq^qWPauovXcRam zB7d(Rv<1kIUq=xV;BlbVKO5NmCY=LbCHguE)C7F>Rs!yiay0#yK)#5~`A78qM+kCd z30&9^QENRofv0%1>10q{IPtTk}jEWyGL zm%8X~a{+Lo!oh2A7aLg{F89!WV+4SsK1Xf}IR&X}+c_thyy7%d(0xFjsA>h!V(MC4Sf;Pjjo5^u z(hO`dW(#e3N$KryqpB2^W)K@^3uRMPh^n3f>Up$v%Elg%4Vc~ooE{r6wg*7R#7a*y zCOdz8WQMEX{6$iVoipd2X5aqUke)XGV3E47y778^yk2vJ15YFx*?>fw)g0(BakMrM zGBV(yx5G`-cfBEprSaQddOO^V40vd59yGPx7Llcu&2-JEoXJ_snVr9lapR`}W60|b z_qg->Sit`ZqMz57c~?vv<&_Q&=C390U#|9Z<;!kny$hRqW~P1oc&Uq)`g^NmIR&Yd zS2|2V>-@{SJ|I7sYu39!?Wb)Us-QS~3`b66l6N?b)bw}&rrdM%_xwXo`}l@WXnIxx zXFn+;At}xTvJWU05nW&5Uj#lganE`e`2LGd?)OcFv_=mVrSaB<65nCx@?G7)sIxQZwG+As``Pd?kCdMHx~2@R(7Zcj%`uT z&I7ltDyU9GTG_38rU1|}K(3X0ldNIyx(l(Toj&A0s2?RUP`6gK00000NkvXXu0mjf D(CZ)9 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy.png new file mode 100644 index 0000000000000000000000000000000000000000..a17b1467292e830f8db47a16ee719af75158f2f6 GIT binary patch literal 896 zcmV-`1AqL9P)u4Qg0shFX*ZGgY;C=Q|nOK^0g zXC!u1bjtI1-G7$R*&{7!%*%~SIzPlJ?yY?4b>LOb5BzwgI%oHFUh`I-~c zkMlTAxf*3cQgZ`v3F!4=6!Im$oc<*^9^M-3!Iaf{iR76hhP{dDg71%KDF1-od^Ogwnqo&p z1Cqwvq<&KTF6o`RYobT>n3Oc{mfdPacwW*CNmU(W>g}p-P)FT3Yp?9aV^!a-HSAx9 W$oV~Q(WBu200005}FhM literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy_checked.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..b385fe2a9694ff4a3fdb71c8c182dfc2c6d3f885 GIT binary patch literal 988 zcmV<210(#2P)j!Jh;5eCSs=0j#4r{0Zz$!sQKt zgmndfKcXoOIR@+k(ku*l;ItYJ^c|F9tl-r^Qz3i~d{{5s4M2gDYWQrvsF@$e5RN}1 zvB#JBNJ5TY3)EBz`p^&Td`4pPMR%8A+fyNR*?4sU3DPM3D7MqPd9RcVfJ^=7B?hqG zpJ6+I3Q`DYmGq#aiLj9gxbv#Kct^uUF!0(K`VV4?~ z8HApQb_GD;7Tmo7#VME?jy`$#0H#JP=k9eV*_N`r8l7l+#hCF@2^RVPfZg0*{KNuTSCHM-`K zKFdw7isgn1S))qM&9f@BcGyf^(GTsf#cZT@ZAHK3wsyoGYeJNq@jPz2;aL^$JHv2q z)F#ao<4svH1x}IXO4R^fyH6@=*bkhnQv}Uej4zB~p!d@!W=bMFs4pQ$f!(p; zZOlZSGKPWIYniCWxE81>!!~CV*EcCP0(?T6EAwyGkP-grgeLtNwxfLDjBr;CBRs0Y z`RR~zZyc8Lf}Ho9R}D0!uu_HPK+oKSv3)ozN)C^)-T^7p+w2ivEgY!k*UDc20000< KMNUMnLSTa1#k?i} literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like-unselected.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like-unselected.png deleted file mode 100644 index ba084eb8bbd190ac1fdb69a4b526989ffb858bae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1192 zcmV;Z1XufsP)oyR^z<}9cGx4Q}c(Isl z^THby9bPQnsEg76!^8{kOg4$eXkrwyO-P7~%!xDE8*f{nV=#Fs1KcbvFKyv?;jh@$ zQhtTLjPG4ff2Yr<=l4A4InQ~71V~jgfKs3s$TrMAU=kP+k>F;>kPU(f@FMUU&;Zl` z_B9^&13kb6pbfYrB1$r)s#&V~k*eO-jmxE~r&Kj78Gxdy@2aX>6Q@a4Jz>0^gg_Rn zYNsYo9jaQmS;)f)j^bLHP}RDPLN==EoTg45Rc(kD^4SzahQrpGN>8d<3|t3_NRK9g z10r%S+-QlCAHPZ?B*5c9OJp{%L_E|Cyh8eQ1Zax->Wu_EoaSgoPJuEJS%{42dyf!g zXbPNsD5A!EZ~;&8Xvqash=_(F>Wl|+c50sc(+jBpEeDM&X5!2`B+m-y@c^7}gAIi+KQR{S=8Fm{`Dyr%Py3Ee* zU|@xz-WkLqRMhOmVwF@ZDrW|e8;?hZ%|caeVXD(qu7xck3oy3@I6W4?y#+wsVx%XZ znUz01;p6PrH_6#y=XmqeY}=j@*Jw+NLE63?!0YjIw5f_+Ph^{N1F|hfbD+nJM|bBa z6C)l5duHhPZZPh!w0}FuV9yK_BObatN6ot35Rv83V!B|eoZ0hV&g{MH=GUJ_OhaBg ze}}$5+);mP9sL|$=CzvfI8^UocUj)*=U>ha(|Trrd2e7%AK#pxA5Ztwb?(lpZBKa~ zhw2?>LE9qBydj`$J+FChfR<0MFgrEBYO~p`RMi$zTA5E?K_&nXW)>K}^%u9UO%n(! zND7LwIPr0vnPLQkX}^eQc!hro_{@y=yf?u2U-WW!=s{d_bZ|-&U+2s;Ac}x% z#^EX*8V3%F$aJi8Jt8t?98JxmIk92lq&tLsxNJ{qgQ9N1e2{XRXw=b z9%-SfwkHwtysGX>WNW-B!OL?9d1t zh*8fj0XK{)s7*w=N!2}56lg0@V&vXXH^LisA-25PLH+>^a~c$N)!fYh00001BCpEP)K>w%<*q>Ga7NU|j@>SHWQ zdg*a4NQ!)lP)O1@k{)U8NJ;9KWK}B`lk}scygnT{NmnFAs}cJ}Qd#RqS<*nDsCSJ& z)ktiq=oR1V^}bnNwMR};pIE6n#o&|QZap(cg09f8neL9Hgx@B^&O0^LE zR#GesYy-Od81tn?ejoS`do0J%6WjT!Z#%|($a(sBijmYb==9Wgkau%=?Nk6RAUanhKF+X3c zZvB4Mc^3*9VvUiSwW|eW_k~2)1+r645N#5$EnpmY>c{Bb7pLb?94GJc z$6$)oJ)7CAL&^@8LH3PwgTa3v96S#+d6B!3c04*b`t~B>%NZ{$rDw zDB=`c0Ah_{T00u(J&>UHKmxOT486xgV81cOHA{;214F>3pr=Z6F2(#j3k!cxUuMU0=T$u&_g7Ub6H@|RZ?TOw*n>hqj> zt#}~mSk*DnCwiQhblVHND+S@(lFmt5v4c#tS=BS@glA{1mD#u~>N~3ydjl3%>X0S~ Rya)gQ002ovPDHLkV1fi#lI#Eg literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like_checked.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..d745b12b8736d995618921c79909ce7dbfa03143 GIT binary patch literal 980 zcmV;_11tQAP)F;?Wdy~=Y-SdNgX2a}5GNWRD`OLrXfWzTlTJFOyX(5B zG7qb(AHFUWRMmg#KUKHxIrj=h#D7P%LbB8$%tfe2sqvRy0|{07ROngd$Vj2RB;VrQ zBU)yJt-vOr0f+ZzQ5Q}Cy9%Ydh*03P8um{nHIvim!f#)R?BmDm z7MEiW9W?<$8(M&EUx{oo>5eq4o03w$8t;EbffO5$Mk{Gry;sr(z%Tu8MFz0U9%2iC zv^!b>Tm#nRLYfFz6_6hMV$z!i)<+cPizn(JSvf4;3eh<9-+||U2Qjl&K;^=TKc7xQ z|82+)2cQkEQWp`Z^&KP-_H2&~U`9QdY2c1c#nk@j;l^zz1A|>K+!@$zjvD4hd~Y2O!mzGo?n9MG=(Qp~`ik>nd0_WRkfBGtvVi z-9?`MH1uktEzUJFz%3E4=!wRtTbw!A9zwO)$SXDo|uzj+4oJqv>7Iu=Z&A-3x1~#L}myQm!bC`80iPGz^?KRG2Xr2Ymm4SP6j*}bh$XC zpxYh&8TbR39#$Au6sFaH<<9}xB#ga*Y$~rblE5!bj$8*A*J0G=-_$7QfIT@M7s!r6 zs<$K^DsYAvHv$c~@qlF2a1c0MKm?_X7^jS(wQ1kPFeN!=P+MG%0Xss|Tig(J#u!>x z<{P3u#zRL<2G;pw;<6IS9ss`*4dyYmG=BrQ()Q@)<&xaGD^kLD{)GDb$n|%OmFBTocmRz*}0000w?aiKa9CO%n z%1qw7Z?hNsVeP%wx7ON54kRfA)B`m@sT+O=M1kjKHk<7lmLZq{`+*Zc8_*1_U1a(+ z&<)%Kx`02-OypCNHcI+lQm-;sMABJF8}k8Zl1@n)RPGd&bkglkP9SR}{i6KohNP-& zA-~A67Z=k@NiCT|wo4k*%4t+mTe^^YR~RxGw#6$vNvZ)J0M!&mQDCQ;y-FSo7Ko2O z77!BP6X1u`Xb_}K`W84u;dL13Sa9oI0De|rZ>AoBZDux^>d`Yr1erJjr{D9aI}Z`y zQ;JIwpwY~f@Mvo~ki)SFMusNSUUg(>lHu4yIwlRkK7f!LIOZ2%;p*kb{Bt)3!0y&C zN50MPy_i65!tN1H=Z#m$i%akIzE_ zJiZg7FY?CMtL*@ROM07qEM}(W78r{!Z#VtnoayPfFX-ozN&=Ueul2F0|Ez+VEt}@Q zw{EW>TvxWNUAV4{+J^G~uXF7t>UUK5g02JVO;RiHo3GXE%p5(BUSomL*u0sb#P6WZ z#l_R^*GOV}^JYS8f^;rbt4TTvT%)*kG+>n|0LE4U=V$;7t^&|ObK&Wo@69iOrhw9r znaxV-2KM+^y&0Y)+BXInR^}z4fU0mQ<&_(JO&*$AJn;*?=>s|(o8Z#LznPtx%gRNe zH36qD?4`2WtF-?$InDdYHyOzmG7*c@NKU$vlakv&FR*RNRHmtt-TT8t`$n@%G;7Of zXsYy?+5`NRTGX9Y@u_pMQl!^)YE#GBkLrc~5m|4t~CWXiWMuB6gkjwR-+FUParSw+PflNDkEv_Y)Z}m&s zm2Ho-O484HguE{4<6O4JJ96yZeo5bur^kj3lD?DFlLg>YNu83`lkevn2mPgs4(-6s zCGy!R;GxTcy3FiPR_dK;0q7E-&c)u3-0*nPgIGMSUQvU#BXD7zi#0ZTaxJqAR_!k(A#HkuB+> zq$T}379~we%4H=hNV+G<)864qnv>L(h3uiENc%@5X+DwEjPd>fk!>fPiyg0dGfxK( zSJFh>HZG}sc-P$C=2fo)Cutu>folLc^HX0DzdIdqGn&+pkVa@Xu1gXF9Z6|FRY}=x6C|!@U z)HJWT%KyWvWBe>iddG$z5;USr-JE=me21NM90ALYG0&>m*1u5m8a#ixNX=_xptC6U zI9$3iO0he3yj7s$0AGPm@q|75cG%N+k^$dsg8|Pb<{L>}CKeo2#CVV; z*L0a!;O=vbv#Dg;NOO`Vj8pGrLB=!3#F*$YEos4o-Q9xlf}|TQ+t?xx8MCS}>XNau iBTa4G5%ssyn*9N=QE0&6_Ni$A0000&G6|tjNc2-v}!LjZ-dMP4EKSZ2@Pwr zN~;lA8N47xaF_dJ0LzkKmV|uN68{V!(E@H$WQ8qiSYZ&7A6nUC=G9b_kA@^$!^ z+ZmV~^kcpbLna+bXhd1#pe&D^Tnq2-Ld=Eybi}|2e0mI%{ZPpIcS@kiDf?U$ApgD0 zbV?9Y!BiTA&-_1pc2H8miV6LXCLS)9FxZ)rV__9ijw)#pCh*m|08Tw5_d%>e!op8T z-HB}PgeYkz7UP4pjD``$8(?XpUu8uV=KMPmgd-Tn1fbv3PT*QtRALnvH&`}h1Yxs~ z8IKU3(6QyT+;D>C_8H*_u%T!c$7>>%!70nlx%CuyknUhp*BaA6&(1TFU$74oE)Rk0 z6*RBft$&qz9u+O%b;qTkh7-V*8j&h+*%*4;kN!4O65>H!v77_CfQG+l<5gqm-CW8< z&0@$?lZRb_P25P)b4RlV&asD=wc)p znu&>InJk&(jnVk8=sSqUM9r2Aj0p+ECW}iPGG6F#B~w))xU$y7{&b{Az?)iQ1Ip+#n098!|N`XQk%QVjcveS=#J1WP`*6cm;SHs0AEA zdX(h>zy(|ZI)R@>MA=TL>K;}7R8?>5#xbm_pQ!4d?I0+sdRA2@bn7s#svnq7XG=&H zs_JFkJ~&l%|7J;6Z*df(VN6x4HcGNiRcCeQFr%uqiIRMAhe<9cTV+X}RJ9QJ4JaTv z7zZjv&Ltp>)bMa{tLBp+`Ar`LV-rU3t$sOM9q8{2A<~8!Z1)SA{vfpO(G=kdhg+zoKN_wzR7v+dhaFTatL?{Ak{RS zv?j3d{Z}{n{Z}sl&({?3*86r#uH6?0>F*f>;K&OFymR)5)!MBA=nY5oH7gz?!@dYf zfa_O1cs>5Oe%?`kge1VtU%dP^{GZjR)&Ur1(d)3{aepzijx8A9FR*q^AP}@lv_e%= z46`y%x8hOyY#xQhIg$TQmE}=Xk{j2rs3ezzhaP{poe$#IzIQZ!A8Kb{vjIIlVct`y!-bP&) z1|!E~og)r@TwXoB4D`h5qv&WG!0nn^+s}xgy8iGZ+OhJAd>UIEh{V<5<05h`9MN9j zcC6LC&XE-)t7{LCZA*{bL>MLZycIbZK9+4ur@Hn)#O`)Y(d!(EM{)@0ixkf0Ea>xC z9@npWRz%}OV;PO-9g&R$VB4F)sdGm;b?zv(y%`aEN~%HQc?TyN%P6nNU-z6*@L70H zwOsJOh$U6_%6$M77U$5=d>9d-sMJP%lN~XH%&autYpEbJD{aMZF@*XiJ4K~7M1+Rs z!;yL+e9kEF0AEJS!!xm(?rGnGhJv9r_PN=B1{Rmr*aM48IyW0wV-E#GI_+DC*W)+E z8-_VG`RKATfG7a^&E_f@@Bqg|WHMG?kBE59)+8ANzRXw)Z8R>pi{b3tPcB|^XtsIS?-s0$v#+0B<63$fBk5zSO69nC=`jM(; zZ2RXMlk{;@9qNF}81?KTaKo&EIz{9fJN2Du9nvX4iK)HWrrE#jgII907x^FE>BpoK S_;aZM00002*TKw?@N z0s#~7rIAGZ0dG9w9|8U#{s9^_KH!rkghLwPKEyXSDcH7;ZnZwK7 zT6br=3vE4*bH8)X%)RIPJ;%@lBqe|zpbOXttg%azz!;DQ{xZfCSPUdZC7qNsA}KA& zS1o)=f7_Z9lA=#S6p=I}X;hDo>yq|Ka+)QJOZq@kUQdpkq_dJ*nj!mGQlP~nkaVst zsXfLImxydW>DkcZwRe`+(jzBnFief3T0Z<%QE%l}-vatsK5PTt1aNFqzfrH!^8#{H z#fCfPriyr8uu#b{NpTlg4{Q#rC>4FaIQKK@RF1u`_44vyFUC~gJ^XlSjLToANcOJe z*s14<#v))Pn-ZmUP=0Gp8X-a_*7yg-F~ zsi6!D`I0=ZVGEyj8;KzjF~nON&*NrlD!+M;{}1t{i|dwiIk<-(W0)6w0=-UFNP z%0AzIbrXQ0*Pq3WM!0<8HWTA{4h(N$)zfw7bpVXH);u#-tu;M(@%#;Lk7h~ToZ_kW zIQKKtT>oie?ikxUTe#8V)yaxhFb80OWeT*?+AkpSB{jkigfpxrksEErHU9IHIr*v zY%Itv(&f)IB%4R-NE)NOF(i7tE9r_2y9)*3E0W%kwBUR;&1O|=)Jbb+ iYb&#{D(cTP+UyZqs7Y#NCI*K90000T?^*z6RLQE4Igpl>c6&)H z=TDz$ml56p-URjlPXMMa%mVYkG;q->qX$mP+J*pcq%{R?)I zTfkpVX+$lb1MdMbHfPJg*J}9m`4L%d3OJG0h=nhKW9`!2fhllS4Ifp5n(8!K_~Idf zt^dq%v7Ek{R?`3&hC{%+4+*Rq^ynHKtVyZQ#)(Z3q*J@K`WUGfPRN=8@TtGIWi>jf zSaaJKE^q`u#?u-Ft^h9vMS4*P_rda9i%kXMNm$K6xq0hfH}Hz5&@Zm38^Iohr{0F{ z9=JORnM415p7Yle%hYh4x~;7)>p)twhwi-At<~!$Dsr?%N>PB=7+-#;X6UBO(0b8SM%eID+z)FNC+X5W-U`T*zC_ zzkE{4n^N8nv}#%-3jHeV00tgR7|5lCN(U@cANbRu1`NL@(fZ^00000NkvXX Hu0mjf!*tOf literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-suprise-unselected.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-suprise-unselected.png deleted file mode 100644 index 2cd8811c68659435e4b070e5290a6e308f0142f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1339 zcmV-B1;qM^P)6w zyaHs!=sphI2Cf2iK%6EIjQPmTw+M|i9TUEbO)ivn=6jl99Rh^nT4XWxVhIdi| zS*WUiX!_Kks`<%6zM5hy#?pYQRwfF$S5>{bbQ)3BJ&T3hvBZ!wVJjCDo>a9EXafpZ z9t{H9MdZJkjixB^v340D0bT@7M@9ovM5j-Iw^@Ew14pB}-Uz_=mf4z-OJIwL_#!R( z#R`H9FM-czS=3k$-N5F^-T~hv_2=#~9+;&1U^#iuuMT-2s9gKCm;2prtouvZv~_*d zxBY8(aqVI+#btR|-!DZ>At$7=ot4@Zxi%NOi1&2ssI9Y>Fw#)dxXYC6%N;(Fwx=430~ zwqZJ2hG@Sr7;AVL@H#;FGxo>onP0RvWOs!Tm!I12S}82frK|nnbe{rqZcNYub1p;u zqX5{NoQwyiaNhSa;_^@XG{0zVye04fa6Rn$TR_ZTSljgh>d)Rqb#8W6T$ab-X9*0cdS}K>gXSkZBDSZCc02 zCn{n>&V)VtLmQ4B_bkZj1Lag%>)l0FYxcGV{2Q8dMxTo>`CpUUz^|&-lFCcg#D^ zh}$1ADL8sO1cUSD(Ahw#$v8g5g{-UT_XRCO!R#mZLMg2U4?DiofK;=t_{Y|VBNnF=qst1B4v%FHzHmlX_J z7n$bSfi20-I_3@isd92tvgGZ6MMN|cKtxnkzXwhwGwbCGcABm_K;X5vHuBC#TayJn z6|Pgw4ER3*&V zBK}yX&|2X4L}uN%C2v*&T(Ds+#btSkLe`7Ox%mnZ1wgBDxLTCP15-MYak#t=)tX9=8Rn?vZV3(>MSJh|Ietly=zhM+E_5$1Ew-N=IK xMC31)>YgbIbOumj#NKe!ADwX_HkIs4{s%H8YlFIW3#R}8002ovPDHLkV1f|rgFXNN diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise.png new file mode 100644 index 0000000000000000000000000000000000000000..fb8f6887ae611aa99e102f1bb87ef9c949143299 GIT binary patch literal 974 zcmV;<12O!GP)u2PA~V0WP$a zdMx4w614od!=KTHfH+eK;T9F5N`Ro3s$5B=hS;Q4VpSwn44-(R^p#%zWqjea|2UB=rI(fFa-{Fc3~{00m$LSTYR5VLylU{54ykX949Xu>a=i2?ooSq&2Zv5WHRWAc)nhrp5-J$9Q+lML@T^!qwyo_V} zl#1^5xatKI*BvA^hm8Oq0+?Y@zu!E&TqrX>I?dGh9IjL2-t1#0#%8!Vk&h7GoX9gV zHp9Ky$GA?7sqs0+N2ggXlv_hTkd*EMUII=v&-5McA=R5;;ck)ZkN%?S*6^HwmtQ*; zAsiU)rI6d?mmlZpNhjG{cj+HY(Ra9~HS`p4!UTqapdCMzdXRyh@EmDk_#w&EJX; z`u-NSRSk0wo*lH!b;yx5fyg_pEnEEjZ4TEC$ezv6Ka`@7D{?Zw# zDY9oXcuv67^*n3KRy)#xuAREhp-eYtUO&e9cTcmrZ1MZ#Jc(qqeiKQP7f$wZ;l0xg zy?BT}Zp^c^P~NqjU2a>gWlurvi8wg6uc{YB;;LRC$M(0!L2XM`+1sfeKk5qgtlpZ( zzUTk{%hJ!aSDo&j1oR}H+_=k(tPN2I3T7ytYdm|b0V^i(FR<8=Ue<-`>XdK+STYU6 zaDj=qt16byuakLxo6J+O{7wjeFbuvVeX9J`J;|C#O-biMrS24WCA|~dCbqF2pGdkBn%&)u@Eu7X wOX}KfPb02Z4TX9*l(UAythZQwcdyO<13JSC+b}VYLI3~&07*qoM6N<$f^OQypa1{> literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise_checked.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..df9316e938916c41de313a52b2230060427b3548 GIT binary patch literal 1017 zcmVFBZSzKh@dSN zLGjX?7TSR3AU&8Oda!X5s-TA+^il;$Y4KJCZxu8bK`*wJw&J15h9uTdw90O_-7GtK zpNBWQ-QC&j&erx112eq$zW@8a`QH2g6%lY@MiOd+G>YvgG6>s(p}&Eg2&5cybr)JP7Omz~Wc1 z6dINi%U>hgTx@DUk*n}zXE+b?406~&2;#HjnM+zVw?4`UxGpomd*tm zcomEvo@ad9T+83W?LXkrgLZS9ol13aTE-E+Y}M}mE!#W*u_TxZm~yzJ+6BKD#EjVz zSMn_h$72Y&Kw=vHWAlP)z)*(e#!ZO72^koHoEBj<(6jrE8%kK!j>B>wp7$H`Xjb&- zlqvydqps@N3YkOpar?W_D#e+xDOJ)zac2Xi?uh;;A(gSu){)43mkWl|HF*j=$Yh|@ zZBfpHY}n=~=Oe5CvFE3i4^TUYagQHL>wE#c-A)tpSK(U0zTaAor241mW3HvexG*E0 zGJFgiZ7akQ#q%M+r&{ygXr0ASEj*~F3@3mIAo)K`)G4hwKHAPiRdFRN+J}9CO&sZx z>@skSKIX>WiC`1!@zY6hy)_Oa914u^KpP`GFN!k-!|7^+bnU!rUNtLmfniY$0-2h^ nDE zMRFd3=>!!c7D=m~MI=}%BtZxwT$3&!3WA7*2o~0M5wY=Rb}S4pryM44U}1k|=41G0 zq%prVh|7?}*Ok_-&yH2t*P2aRXuEG zYgcFQo&%8jbhF=+w1E>lPP_;C-pdOSITB+W0s_cScb^o1vFU4r!F&zpI1u5#_fX_Q zj1LMiI@!o8$cW>|fJ(2NS%G}U;vjb? z#+xT~>VX`EWRD45bDP46hOx7_4S6Fo+k-*%fF5l(^#Q z;uxZFzW1W8pR=P#+r#H!O0%vhBrq>lTsUt@&Vq$qhMQ%-8Km)uxq3u0w6NF;HR>(! za1bfJediGGQ&aVspKFW%&6@PexNY0d13J$N{y)CUpxk3!*u#8hT8MgWo9Gc4wjFPt zw5@%)Bs;@v)#-27MWyyv9*#WourM|;`PzAo?$V{pmi%gR-d%aLq(SoD|HK=I|0z8C z&=9QiU!Xoou6W1IFR?+khhld(=*3&~TgK$`$`)4~+VXa*_Pq5+_s-T_DVKcl-b1Bl m1zU@EO?tItiPdZGI_Bj6mVdaEz8wJikHOQ`&t;ucLK6TL;ECP< literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-selected.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..7e430e81416f04d2a0767677de0533281cf113c3 GIT binary patch literal 553 zcmV+^0@nSBP)`M%3Q>m%dM#_|z z7CKIolR=K2%U;;PvNGr z-9+1Dz*DiE_O$R$dSa(;Yn9E=7CTMLhyyKhmSfxVm~J5aqbvpHV_bb~LO=FU{d3sU zX{Dm0Xi)$&7dNsp3%1Yvh18N>SiU^5bfogL`QFg$ti_!1z#gO)tPr>|@#JWQ`o@{T z^{nB(VepTnqNJ4Z$>+x^zx?G|?wniiFrEbu?;zYm^O{L-M6;SC=XnysWb2_A|$!x?oT_$*N)P&Ywb3({w@W0N+7wmeDZVC rJZvgGy_%^)0f-zJn_lVN;(ziDqs5nb{(*;900000NkvXXu0mjfmY?|O literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-small.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-small.png new file mode 100644 index 0000000000000000000000000000000000000000..9ab11872315550f7b4fcbdfecfa6e35d6288903e GIT binary patch literal 516 zcmV+f0{i`mP)fz=FXaiS7ZuhP&G}AF$(#~@NX==o!rJ4~`wtDOM}}T!*dY#! zA)7wM8l-Cv?oZ_sz{lk`xxYI2stvo;AvN^J*Yy;wN8$MVTrJU=ySdV^@9^*vvRfHc zLZO2ycMIdv!oynor*2(wtIfB$@6fTAnD?p!9Zpp5rBsYPc{J7P)NE*eb)}IHRFA$u z_vdWC8j$D2i%;jLTV0Q-8%~XkZV#OW3WD>`7iPZlKkywmp$2lZR7x@c0000IqP))_FF2L7n83Mji*aHYX3T8pg|oVx z=RNPamkSM+0bJ<6=D7{;AZC|=BVf&-FF;0O?9R=>HGuP%hIX6BMW74V3#**cxd~@f35g<}!DBwM)Ytw%UXfF>^p&vkhQQml9Y$Tj&!paE}z>`gWtWMHh zKpMHlK+g5mfmJHDjrB!V7bJJ^#D;boW4uu)Bg2e*u*l z&oR!jC`%`_iHU?^~0M&xId8t zLZP^j384~g4wRaIIQp$tuU~K5pO@eL3|rfCl0r%G(qMWFE;T=M>-(1f;Z{0!syBi( j4)_?!XR~AL{7?M?(SF=%7c(9|00000NkvXXu0mjfr&0sS literal 0 HcmV?d00001 From 2aa2a841f16cbe7138ba1a451af0f4c02939c60e Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Fri, 9 Jan 2026 18:44:29 +1100 Subject: [PATCH 022/155] feat: make tsx feeling selector use new sprites --- .../src/services/juxt-web/views/ctr/newPostView.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx index d0791958..cf25cad8 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx @@ -7,38 +7,38 @@ const empathies = [ value: 0, miiFaceFile: 'normal_face.png', sound: 'SE_WAVE_MII_FACE_00', - className: 'feeling-button-normal', + className: 'sprite-feeling-normal', isDefault: true }, { value: 1, miiFaceFile: 'smile_open_mouth.png', sound: 'SE_WAVE_MII_FACE_01', - className: 'feeling-button-happy' + className: 'sprite-feeling-happy' }, { value: 2, miiFaceFile: 'wink_left.png', sound: 'SE_WAVE_MII_FACE_02', - className: 'feeling-button-like' + className: 'sprite-feeling-like' }, { value: 3, miiFaceFile: 'surprise_open_mouth.png', sound: 'SE_WAVE_MII_FACE_03', - className: 'feeling-button-surprised' + className: 'sprite-feeling-surprise' }, { value: 4, miiFaceFile: 'frustrated.png', sound: 'SE_WAVE_MII_FACE_04', - className: 'feeling-button-frustrated' + className: 'sprite-feeling-frustrated' }, { value: 5, miiFaceFile: 'sorrow.png', sound: 'SE_WAVE_MII_FACE_05', - className: 'feeling-button-puzzled' + className: 'sprite-feeling-puzzled' } ]; From 13b552b1191433ca974a74d827c568de0b632ec3 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Sat, 10 Jan 2026 21:06:01 +1100 Subject: [PATCH 023/155] feat: add other sprites to new spritesheet --- .../src/webfiles/ctr/css/sprites.css | 52 ++++++++++++++++-- .../src/webfiles/ctr/images/sprites.png | Bin 12158 -> 14638 bytes .../webfiles/ctr/images/sprites/followers.png | Bin 0 -> 464 bytes .../ctr/images/sprites/memo-input-dark.png | Bin 0 -> 421 bytes .../ctr/images/sprites/memo-input-light.png | Bin 0 -> 406 bytes .../ctr/images/sprites/post-count.png | Bin 0 -> 442 bytes .../ctr/images/sprites/screenshot.png | Bin 0 -> 535 bytes .../ctr/images/sprites/text-input-dark.png | Bin 0 -> 591 bytes .../ctr/images/sprites/text-input-light.png | Bin 0 -> 553 bytes 9 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/followers.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input-dark.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input-light.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/post-count.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/screenshot.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input-dark.png create mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input-light.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css index 0dea9d06..810c1b49 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css @@ -88,33 +88,75 @@ To change `display` (e.g. `display: inline-block;`), we suggest using a common C width: 28px; height: 28px; } +.sprite-followers { + background-image: url(/images/sprites.png); + background-position: -112px -72px; + width: 19px; + height: 14px; +} +.sprite-memo-input-dark { + background-image: url(/images/sprites.png); + background-position: -112px -38px; + width: 17px; + height: 17px; +} +.sprite-memo-input-light { + background-image: url(/images/sprites.png); + background-position: -112px -55px; + width: 17px; + height: 17px; +} +.sprite-post-count { + background-image: url(/images/sprites.png); + background-position: -112px -86px; + width: 16px; + height: 16px; +} .sprite-reply { background-image: url(/images/sprites.png); - background-position: -40px -84px; + background-position: -78px -84px; width: 16px; height: 16px; } +.sprite-screenshot { + background-image: url(/images/sprites.png); + background-position: 0px -84px; + width: 26px; + height: 22px; +} .sprite-tag { background-image: url(/images/sprites.png); - background-position: -72px -84px; + background-position: 0px -106px; width: 12px; height: 12px; } +.sprite-text-input-dark { + background-image: url(/images/sprites.png); + background-position: -26px -84px; + width: 26px; + height: 17px; +} +.sprite-text-input-light { + background-image: url(/images/sprites.png); + background-position: -52px -84px; + width: 26px; + height: 17px; +} .sprite-yeah-selected { background-image: url(/images/sprites.png); - background-position: 0px -84px; + background-position: -112px 0px; width: 20px; height: 19px; } .sprite-yeah-small { background-image: url(/images/sprites.png); - background-position: -56px -84px; + background-position: -94px -84px; width: 16px; height: 16px; } .sprite-yeah-unselected { background-image: url(/images/sprites.png); - background-position: -20px -84px; + background-position: -112px -19px; width: 20px; height: 19px; } diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png index ae7c73bc4415c21297903c015994c2ba3a14869c..9de5321cecc09a9185e8a9548f7a05a96fca1d58 100644 GIT binary patch literal 14638 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]a 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 literal 12158 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 P00000NkvXXu0mjfyc@7A literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input-light.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input-light.png new file mode 100644 index 0000000000000000000000000000000000000000..dc82bbe1b335f4d5d206452827eca5c36c54c70e GIT binary patch literal 406 zcmV;H0crk;P);iaDdU@0D4hF9p|{h zP&a@Q4)KI4$~eX)hC8*ejW;}CHue61t}#{w=!=CrOy~Ix^XZpk?Vw0O4UeeA(keaz znoGT&u-gK*BEOp2Vusfn;E&kP`Ias01~3%`i+I6u-lqaQiscpsyLgU*5>_&>oHLbV zw9`(3G;Z5iSPQTc3wu8SB(w_*w6QRW`{dDAw9?O{eTG*ob!wp*GJ%_b1~F5#h33!t z8#0~^ohF|VoM030Kg_>0_6`H;=KjFKpZzGl0J~;T`&OXum;e9(07*qoM6N<$f{j$B AA^-pY literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/post-count.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/post-count.png new file mode 100644 index 0000000000000000000000000000000000000000..3adff2e27bf4c196560f709574b3495525983cdd GIT binary patch literal 442 zcmV;r0Y(0aP)FRqF2sSZQ`k1svA}H#*n_Y_>;Uf304h$d6{my^RH_RPH1JCdj zQ@3&wr?~V#X0eSid{50={KKFkH*&9q!_=;@gjx;X;^0F*6k~u-sqf-3KH?P~VOgwP z;Gyzp;dfx?c%JTkoD~7%CE}Ub?&Ax3_=`Vr^?DefgH& zGBzV@=-z-kMYjQ+EXjVB=04HP k*@1bS4EY*84eO6ME^pC z4LO)=v=ub8wnQ}L+Ta`-4myZ|Bxcam68!)%DZHnl=gQ0BbnVtTFC5Oxd7g8=@ALcA z{9X;5#S!JM7FMtcgZPQ8qH7<`Ixe7&S6D6Cq7jVYd_8g3OFSwWbTdFbJ*r)`_NHo( zvOtq~is8c)>;DOQf!-*M;mEMAEvn%pF2%=+)>@Rtawo`P7R}&cWaUPb!RMUL7>;B6 zUt@GPQhgt9F%)&ypVQmHR&;=_L34P3@0h|FEJh5LaID~+6S#wGox*V=TE%Qwl3^DU zITtU6Y#RfK>=eEvE$49pyA{u-Q_y<2Y7gTD2kOP^(ESjwMnGCvz^9NuiP#o`GW@{h z4zBJ?VDIoT_VYN3(+T()ZWn>}F!~p@(-(=+KHT$MT$=bAu=hov`(1XF>$v&{c{3Nc z>9mf%bve-HJILQew>ZesDx2yi?xks)CFA^J$*@M;GdLHnsA`nAFxnnWSHhYqeomIK Z@C#o&ldLe-a_j&A002ovPDHLkV1j^S>+}Es literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input-dark.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..73e800ad5e6c9b3317b11717788d10e69e2427b4 GIT binary patch literal 591 zcmV-V0)NVU)5=wT^IA~%q2Z?Y&84gloHs;~-S$?hG*vh=?Q=jkW^M1Xb@Av2P z{(O_a7`9@0X49|{;}}5GY(biE({DRGTcGV2#%FxNQ!JSc(5+I*AzUj*c<^6C_7%tj zR9KG@yhHO8nsvB2rM*DY_ zYY8jmNb|H}OyF#~ZAg!K2<^ z?&WxSdzVV!i8~xAO>;R(s)5dEWf0}Lh85_-)|w#1R5A=23d#~F>c^%3!RM3g;Y_)a1uD3gtlTdb|WyO1k{H` zc?B-Vs7mHwFE(ODNU%0+>(A`T_jN zPI1m=<2in$vZ1Vpuo*k>u-v-hO&i54ESbho@5#L!$?Tm?;NlL)Lz;6%H3bpojpjs^#8~&Licxc)|UC#00000NkvXXu0mjf<`nJ1 literal 0 HcmV?d00001 From a6d30d892fa21c4e62a2195156db4b5aeebcdf5c Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Sat, 10 Jan 2026 21:41:49 +1100 Subject: [PATCH 024/155] feat: port tsx feeling view to new sprites --- .../juxt-web/views/ctr/newPostView.tsx | 6 +-- .../src/webfiles/ctr/css/juxt.css | 49 ++++++------------ .../src/webfiles/ctr/css/sprites.css | 8 +-- .../src/webfiles/ctr/images/sprites.png | Bin 14638 -> 14638 bytes .../{memo-input-light.png => memo-input.png} | Bin ...input-dark.png => memo-input.selected.png} | Bin .../{text-input-light.png => text-input.png} | Bin ...input-dark.png => text-input.selected.png} | Bin 8 files changed, 24 insertions(+), 39 deletions(-) rename apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/{memo-input-light.png => memo-input.png} (100%) rename apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/{memo-input-dark.png => memo-input.selected.png} (100%) rename apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/{text-input-light.png => text-input.png} (100%) rename apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/{text-input-dark.png => text-input.selected.png} (100%) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx index cf25cad8..79d7faae 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx @@ -83,14 +83,14 @@ export function CtrNewPostView(props: NewPostViewProps): ReactNode { data-sound="SE_WAVE_BALLOON_OPEN" evt-click="window.alert('Screenshots are not ready yet. Check back soon!')" > - + diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityView.tsx index 26e0de0b..0ef1b83b 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityView.tsx @@ -3,6 +3,7 @@ import { utils } from '@/services/juxt-web/views/utils'; import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root'; import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar'; import { PortalNewPostView } from '@/services/juxt-web/views/portal/newPostView'; +import { PortalPostListClosedView } from '@/services/juxt-web/views/portal/postList'; import type { ReactNode } from 'react'; import type { CommunityViewProps } from '@/services/juxt-web/views/web/communityView'; @@ -201,6 +202,7 @@ export function PortalCommunityView(props: CommunityViewProps): ReactNode {
+ {!community.permissions.open ? : null} {props.children}
diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityView.tsx index c3dfb2b5..11e09edf 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityView.tsx @@ -3,6 +3,7 @@ import { WebRoot, WebWrapper } from '@/services/juxt-web/views/web/root'; import { WebNavBar } from '@/services/juxt-web/views/web/navbar'; import { utils } from '@/services/juxt-web/views/utils'; import { WebReportModalView } from '@/services/juxt-web/views/web/reportModalView'; +import { WebPostListClosedView } from '@/services/juxt-web/views/web/postList'; import type { ReactNode } from 'react'; import type { InferSchemaType } from 'mongoose'; import type { RenderContext } from '@/services/juxt-web/views/context'; @@ -127,6 +128,7 @@ export function WebCommunityView(props: CommunityViewProps): ReactNode { {props.ctx.lang.community.popular} + {!community.permissions.open ? : null} {props.children} From fd58997123c8a81c8ea7ae3ea3a8cc5e22ceb555 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 21 Jan 2026 13:51:09 +0100 Subject: [PATCH 106/155] chore: clean up unsused ejs files --- .../webfiles/ctr/partials/new_post.ejs | 86 ----------------- .../webfiles/portal/partials/new_post.ejs | 93 ------------------- .../webfiles/web/partials/new_post.ejs | 69 -------------- 3 files changed, 248 deletions(-) delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/partials/new_post.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/partials/new_post.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/web/partials/new_post.ejs diff --git a/apps/juxtaposition-ui/webfiles/ctr/partials/new_post.ejs b/apps/juxtaposition-ui/webfiles/ctr/partials/new_post.ejs deleted file mode 100644 index 260af361..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/partials/new_post.ejs +++ /dev/null @@ -1,86 +0,0 @@ -
- -
- - -
-
- -
    -
  • -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
- -
- -
  • - - - -
  • -
  • - - -
    - - -
    -
  • -
    -
    -
    - - - - -
    -
    - value="<%= message_pid %><% } %>"> - - -
    -
    \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/partials/new_post.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/new_post.ejs deleted file mode 100644 index 7788987b..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/partials/new_post.ejs +++ /dev/null @@ -1,93 +0,0 @@ -
    -
    -

    <%= lang.new_post.post_to %> <%= name %>

    -
    -
    - -
    -
    - -
      -
    • -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    -
    - -
    - -
  • - - -
  • -
  • - -
    - - -
    -
  • -
    -
    - -
    - value="<%= message_pid %><%}%>"> - - -
    -
    \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/web/partials/new_post.ejs b/apps/juxtaposition-ui/webfiles/web/partials/new_post.ejs deleted file mode 100644 index 5c908a6a..00000000 --- a/apps/juxtaposition-ui/webfiles/web/partials/new_post.ejs +++ /dev/null @@ -1,69 +0,0 @@ - \ No newline at end of file From d4db75b84a7397907e4d21743278b0badca2f5eb Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Sun, 18 Jan 2026 20:59:28 +1100 Subject: [PATCH 107/155] chore(ctr): TypeScript for client stuff too --- apps/juxtaposition-ui/eslint.config.mjs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/juxtaposition-ui/eslint.config.mjs b/apps/juxtaposition-ui/eslint.config.mjs index a425e6f2..6e2148b4 100644 --- a/apps/juxtaposition-ui/eslint.config.mjs +++ b/apps/juxtaposition-ui/eslint.config.mjs @@ -15,11 +15,16 @@ export default defineConfig([ rules: { 'no-restricted-imports': 'off' // It's a special compile step, we can't use path aliases }, - files: ['webfiles/**/*.js'] + files: ['webfiles/**/*.js', 'webfiles/**/*.ts'] }, { // Rules that apply to the 3DS (CTR) and Wii U (Portal) browsers - files: ['webfiles/ctr/**/*.js', 'webfiles/portal/**/*.js'], + files: [ + 'webfiles/ctr/**/*.js', + 'webfiles/ctr/**/*.ts', + 'webfiles/portal/**/*.js', + 'webfiles/portal/**/*.ts' + ], rules: { 'no-var': 'off' // 3DS and Wii U browsers need to use var }, @@ -46,6 +51,6 @@ export default defineConfig([ ...globals.builtin } }, - ignores: ['webfiles/**/*.js'] + ignores: ['webfiles/**/*.js', 'webfiles/**/*.ts'] } ]); From 51849a95f03c7ac964687da29212d2caaad7c08d Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Tue, 20 Jan 2026 12:00:57 +1100 Subject: [PATCH 108/155] fix(ctr): use proper types for cave applet --- apps/juxtaposition-ui/package.json | 5 +++-- apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts | 17 +++++++++++++++++ package-lock.json | 7 +++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 642d814f..4bf1ac9a 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -56,7 +56,10 @@ "zod": "^4.1.13" }, "devDependencies": { + "@pretendonetwork/cave-types": "^1.0.2", "@pretendonetwork/eslint-config": "^0.1.3", + "@repo/esbuild-plugin-oxipng": "^0.0.0", + "@repo/esbuild-plugin-spritesmith": "^0.0.0", "@types/cookie-parser": "^1.4.10", "@types/express-session": "^1.18.2", "@types/hashmap": "^2.3.4", @@ -64,8 +67,6 @@ "@types/node": "^22.19.1", "@types/react": "^19.1.13", "@types/react-dom": "^19.2.3", - "@repo/esbuild-plugin-oxipng": "^0.0.0", - "@repo/esbuild-plugin-spritesmith": "^0.0.0", "browserslist": "^4.28.0", "browserslist-to-esbuild": "^2.1.1", "ejs-lint": "^2.0.1", diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts b/apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts new file mode 100644 index 00000000..bc7c3af9 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts @@ -0,0 +1,17 @@ +/* @pretendonetwork/cave-types DOES ship this in its module, + * but I couldn't work out how to import it nicely... */ +import type CaveAPI from '@pretendonetwork/cave-types/cave'; + +declare global { + interface Window { + /** + * Used by the Miiverse browser applet on the 3DS + */ + cave: CaveAPI; + } + + /** + * Used by the Miiverse browser applet on the 3DS + */ + const cave: CaveAPI; +} diff --git a/package-lock.json b/package-lock.json index ca1f15eb..0d08cb11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "zod": "^4.1.13" }, "devDependencies": { + "@pretendonetwork/cave-types": "^1.0.2", "@pretendonetwork/eslint-config": "^0.1.3", "@repo/esbuild-plugin-oxipng": "^0.0.0", "@repo/esbuild-plugin-spritesmith": "^0.0.0", @@ -2288,6 +2289,12 @@ "node": "*" } }, + "node_modules/@pretendonetwork/cave-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pretendonetwork/cave-types/-/cave-types-1.0.2.tgz", + "integrity": "sha512-DFM/osKDWGS4EvVRVDlsss0u+uffN1QiNLlVqVJqhQGz/Fgh14j9Yx9n2rUJNSI/LdEtW7FreDyCGwvJXqbcCQ==", + "dev": true + }, "node_modules/@pretendonetwork/eslint-config": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@pretendonetwork/eslint-config/-/eslint-config-0.1.3.tgz", From 0228d32984483f8655c29100ed1dc6780d34e144 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Thu, 22 Jan 2026 11:51:47 +1100 Subject: [PATCH 109/155] feat(ctr): port XHR and deletePost to TS --- apps/juxtaposition-ui/webfiles/ctr/js/juxt.js | 65 +------------------ apps/juxtaposition-ui/webfiles/ctr/js/post.ts | 31 +++++++++ apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts | 42 ++++++++++++ 3 files changed, 75 insertions(+), 63 deletions(-) create mode 100644 apps/juxtaposition-ui/webfiles/ctr/js/post.ts create mode 100644 apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js b/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js index 0cf110b5..9ce5cd45 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js +++ b/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js @@ -1,4 +1,6 @@ import { Pjax } from './pjax'; +import { GET, POST } from './xhr'; +import './post'; var pjax; setInterval(checkForUpdates, 30000); @@ -277,33 +279,6 @@ function initToolbarConfigs() { } } -function deletePost(post) { - var id = post.getAttribute('data-post'); - if (!id) { - return; - } - var confirm = cave.dialog_twoButton( - 'Delete Post', - 'Are you sure you want to delete your post? This cannot be undone.', - 'No', - 'Yes' - ); - if (confirm) { - DELETE('/posts/' + id, function a(data) { - if (!data || data.status !== 200) { - return cave.error_callFreeErrorViewer( - '5980030', - 'Post was not able to be deleted. Please try again later.' - ); - } - console.log(data); - alert('Post has been deleted.'); - return (window.location.href = data.responseText); - }); - } -} -window.deletePost = deletePost; - function reportPost(post) { var id = post.getAttribute('data-post'); var button = document.getElementById('report-launcher'); @@ -484,42 +459,6 @@ function exitUserSettings() { } window.exitUserSettings = exitUserSettings; -function POST(url, data, callback) { - cave.transition_begin(); - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState === 4) { - cave.transition_end(); - return callback(this); - } - }; - xhttp.open('POST', url, true); - xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhttp.send(data); -} -function GET(url, callback) { - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState === 4) { - return callback(this); - } - }; - xhttp.open('GET', url, true); - xhttp.send(); -} -function DELETE(url, callback) { - cave.transition_begin(); - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState === 4) { - cave.transition_end(); - return callback(this); - } - }; - xhttp.open('DELETE', url, true); - xhttp.send(); -} - document.addEventListener('DOMContentLoaded', function () { pjax = Pjax.init({ elements: 'a[data-pjax]', diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/post.ts b/apps/juxtaposition-ui/webfiles/ctr/js/post.ts new file mode 100644 index 00000000..0844c9cd --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/ctr/js/post.ts @@ -0,0 +1,31 @@ +// Script for the post view (post.tsx) +import { DELETE } from './xhr'; + +function deletePost(post: HTMLElement): void { + var id = post.getAttribute('data-post'); + if (!id) { + return; + } + var confirm = cave.dialog_twoButton( + 'Delete Post', + 'Are you sure you want to delete your post? This cannot be undone.', + 'No', + 'Yes' + ); + if (confirm) { + DELETE('/posts/' + id, function a(data) { + if (!data || data.status !== 200) { + return cave.error_callFreeErrorViewer( + // @ts-expect-error wrong upstream + '5980030', + 'Post was not able to be deleted. Please try again later.' + ); + } + console.log(data); + alert('Post has been deleted.'); + return (window.location.href = data.responseText); + }); + } +} +// @ts-expect-error window method hack for onclick +window.deletePost = deletePost; diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts b/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts new file mode 100644 index 00000000..e224077f --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts @@ -0,0 +1,42 @@ +// CTR XHR appears to be roughly: https://www.w3.org/TR/2009/WD-XMLHttpRequest2-20090820/#the-xmlhttprequest-interface +// responseBody is missing + +export type XHRCallback = (request: XMLHttpRequest) => void; + +export function POST(url: string, data: string, callback: XHRCallback): void { + cave.transition_begin(); + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + cave.transition_end(); + callback(this); + } + }; + xhttp.open('POST', url, true); + xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhttp.send(data); +} + +export function GET(url: string, callback: XHRCallback): void { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + callback(this); + } + }; + xhttp.open('GET', url, true); + xhttp.send(); +} + +export function DELETE(url: string, callback: XHRCallback): void { + cave.transition_begin(); + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + cave.transition_end(); + callback(this); + } + }; + xhttp.open('DELETE', url, true); + xhttp.send(); +} From 961f7f2110e0b104cd9d024db28e29efcc891662 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Thu, 22 Jan 2026 12:24:35 +1100 Subject: [PATCH 110/155] fix(ctr): refactor deletePost away from onclick= with apologies to the TSX version --- apps/juxtaposition-ui/webfiles/ctr/js/juxt.js | 3 +- apps/juxtaposition-ui/webfiles/ctr/js/post.ts | 48 ++++++++++++------- apps/juxtaposition-ui/webfiles/ctr/post.ejs | 2 +- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js b/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js index 9ce5cd45..f2196ac8 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js +++ b/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js @@ -1,6 +1,6 @@ import { Pjax } from './pjax'; import { GET, POST } from './xhr'; -import './post'; +import { initPostPageView } from './post'; var pjax; setInterval(checkForUpdates, 30000); @@ -318,6 +318,7 @@ function initAll() { initMorePosts(); initPostModules(); initTabs(); + initPostPageView(); checkForUpdates(); initToolbarConfigs(); pjax.refresh(); diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/post.ts b/apps/juxtaposition-ui/webfiles/ctr/js/post.ts index 0844c9cd..b2f1090e 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/post.ts +++ b/apps/juxtaposition-ui/webfiles/ctr/js/post.ts @@ -1,31 +1,43 @@ -// Script for the post view (post.tsx) +// Script for the post page view (post.ejs/postPageView.tsx) import { DELETE } from './xhr'; -function deletePost(post: HTMLElement): void { - var id = post.getAttribute('data-post'); +function deletePost(this: HTMLElement, _e: Event): void { + var id = this.getAttribute('data-post'); if (!id) { return; } + var confirm = cave.dialog_twoButton( 'Delete Post', 'Are you sure you want to delete your post? This cannot be undone.', 'No', 'Yes' ); - if (confirm) { - DELETE('/posts/' + id, function a(data) { - if (!data || data.status !== 200) { - return cave.error_callFreeErrorViewer( - // @ts-expect-error wrong upstream - '5980030', - 'Post was not able to be deleted. Please try again later.' - ); - } - console.log(data); - alert('Post has been deleted.'); - return (window.location.href = data.responseText); - }); + if (!confirm) { + return; + } + + DELETE('/posts/' + id, function (xhr) { + if (!xhr || xhr.status !== 200) { + return cave.error_callFreeErrorViewer( + 5980030, + 'Post was not able to be deleted. Please try again later.' + ); + } + alert('Post has been deleted.'); + window.location.href = xhr.responseText; + }); +} + +function initDeleteButton(): void { + var del = document.querySelector('.header-button.delete'); + if (!del) { + return; } + + (del as HTMLElement).addEventListener('click', deletePost); +} + +export function initPostPageView(): void { + initDeleteButton(); } -// @ts-expect-error window method hack for onclick -window.deletePost = deletePost; diff --git a/apps/juxtaposition-ui/webfiles/ctr/post.ejs b/apps/juxtaposition-ui/webfiles/ctr/post.ejs index c4417ce3..163b31a4 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/post.ejs +++ b/apps/juxtaposition-ui/webfiles/ctr/post.ejs @@ -15,7 +15,7 @@ data-message="Reply to <%= post.screen_name %>">Reply + <%}%> <% if(post.pid === pid) {%> - Delete Post + Delete Post <%} else {%> Report Post From 0c0cc2ded38e8d02cda86aaf46f8014ff9ed379e Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Thu, 22 Jan 2026 16:37:19 +1100 Subject: [PATCH 111/155] fix(ctr): override DELETE method for some reason (probably Nintendo's domain whitelist) we can only GET and POST. so override it on the server side --- apps/juxtaposition-ui/package.json | 2 + apps/juxtaposition-ui/src/server.ts | 2 + apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts | 3 +- package-lock.json | 42 ++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 4bf1ac9a..70bd735a 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -37,6 +37,7 @@ "express-subdomain": "^1.0.6", "hashmap": "^2.4.0", "luxon": "^3.7.2", + "method-override": "^3.0.0", "moment": "^2.30.1", "mongoose": "^8.19.2", "mongoose-fuzzy-search-next": "^1.0.13", @@ -64,6 +65,7 @@ "@types/express-session": "^1.18.2", "@types/hashmap": "^2.3.4", "@types/luxon": "^3.7.1", + "@types/method-override": "^3.0.0", "@types/node": "^22.19.1", "@types/react": "^19.1.13", "@types/react-dom": "^19.2.3", diff --git a/apps/juxtaposition-ui/src/server.ts b/apps/juxtaposition-ui/src/server.ts index 74839fcf..18d6c555 100644 --- a/apps/juxtaposition-ui/src/server.ts +++ b/apps/juxtaposition-ui/src/server.ts @@ -4,6 +4,7 @@ import cookieParser from 'cookie-parser'; import session from 'express-session'; import { RedisStore } from 'connect-redis'; import 'express-async-errors'; // See package docs +import methodOverride from 'method-override'; import { database } from '@/database'; import { logger } from '@/logger'; import { loggerHttp } from '@/loggerHttp'; @@ -54,6 +55,7 @@ app.use(express.urlencoded({ })); app.use(cookieParser()); +app.use(methodOverride('X-HTTP-Method-Override')); app.use(session({ store: new RedisStore({ client: redisClient }), diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts b/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts index e224077f..da8309f1 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts +++ b/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts @@ -37,6 +37,7 @@ export function DELETE(url: string, callback: XHRCallback): void { callback(this); } }; - xhttp.open('DELETE', url, true); + xhttp.open('POST', url, true); + xhttp.setRequestHeader('X-HTTP-Method-Override', 'DELETE'); xhttp.send(); } diff --git a/package-lock.json b/package-lock.json index 0d08cb11..5428604e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "express-subdomain": "^1.0.6", "hashmap": "^2.4.0", "luxon": "^3.7.2", + "method-override": "^3.0.0", "moment": "^2.30.1", "mongoose": "^8.19.2", "mongoose-fuzzy-search-next": "^1.0.13", @@ -63,6 +64,7 @@ "@types/express-session": "^1.18.2", "@types/hashmap": "^2.3.4", "@types/luxon": "^3.7.1", + "@types/method-override": "^3.0.0", "@types/node": "^22.19.1", "@types/react": "^19.1.13", "@types/react-dom": "^19.2.3", @@ -3666,6 +3668,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/method-override": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/method-override/-/method-override-3.0.0.tgz", + "integrity": "sha512-7XFHR6j7JljprBpzzRZatakUXm1kEGAM3PL/GSsGRHtDvOAKYCdmnXX/5YSl1eQrpJymGs9tRekSWEGaG+Ntjw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -8714,6 +8726,36 @@ "node": ">= 8" } }, + "node_modules/method-override": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", + "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", + "license": "MIT", + "dependencies": { + "debug": "3.1.0", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/method-override/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/method-override/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", From f7ad03030f342454a5b3c80d8b8892d05a3b701a Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Thu, 22 Jan 2026 18:27:00 +1100 Subject: [PATCH 112/155] chore(ctr): refactor delete and empathy endpoints clientside nice server-style typed wrapper around it --- apps/juxtaposition-ui/webfiles/ctr/js/api.ts | 53 +++++++++++++++++++ apps/juxtaposition-ui/webfiles/ctr/js/juxt.js | 7 ++- apps/juxtaposition-ui/webfiles/ctr/js/post.ts | 11 ++-- 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 apps/juxtaposition-ui/webfiles/ctr/js/api.ts diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/api.ts b/apps/juxtaposition-ui/webfiles/ctr/js/api.ts new file mode 100644 index 00000000..fcaf8c22 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/ctr/js/api.ts @@ -0,0 +1,53 @@ +import { POST, DELETE } from './xhr'; + +export type EmpathyPostResponse = { + status: number; + id: string; + count: number; +}; + +export function empathyPostById(id: string, cb: (post: EmpathyPostResponse) => void): void { + var params = 'postID=' + id; + + POST('/posts/empathy', params, (xhr) => { + if (xhr.status !== 200) { + return cb({ status: xhr.status, id, count: 0 }); + } + var post: EmpathyPostResponse; + + try { + post = JSON.parse(xhr.responseText); + } catch (e) { + console.error(e); + return cb({ status: 400, id, count: 0 }); + } + + cb(post); + }); +} + +export type DeletePostResponse = { + status: number; + nextUrl: string; +}; + +export function deletePostById(id: string, cb: (result: DeletePostResponse) => void): void { + var postUrl = `/posts/${id}`; + + DELETE(postUrl, function (xhr) { + if (xhr.status !== 200) { + return cb({ status: xhr.status, nextUrl: postUrl }); + } + + // HACK: we haven't actually formalised this API serverside yet + // for now, synthesise the response object ourselves + // make sure it looks like a URL... + var nextUrl = xhr.responseText; + if (!/^\/[\w/]+$/.test(nextUrl)) { + return cb({ status: 400, nextUrl: postUrl }); + } + + // make a response object + return cb({ status: 200, nextUrl }); + }); +} diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js b/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js index f2196ac8..8b0bb9a2 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js +++ b/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js @@ -1,6 +1,7 @@ import { Pjax } from './pjax'; import { GET, POST } from './xhr'; import { initPostPageView } from './post'; +import { empathyPostById } from './api'; var pjax; setInterval(checkForUpdates, 30000); @@ -136,7 +137,6 @@ function initYeah() { var parent = document.getElementById(id); var count = document.getElementById('count-' + id); el.disabled = true; - var params = 'postID=' + id; if (classList.contains(el, 'selected')) { classList.remove(el, 'selected'); classList.remove(sprite, 'selected'); @@ -154,9 +154,8 @@ function initYeah() { } cave.snd_playSe('SE_OLV_MII_ADD'); } - POST('/posts/empathy', params, function a(data) { - var post = JSON.parse(data.responseText); - if (!post || post.status !== 200) { + empathyPostById(id, function (post) { + if (post.status !== 200) { // Apparently there was an actual error code for not being able to yeah a post, who knew! // TODO: Find more of these return cave.error_callErrorViewer(155927); diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/post.ts b/apps/juxtaposition-ui/webfiles/ctr/js/post.ts index b2f1090e..7335a55b 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/post.ts +++ b/apps/juxtaposition-ui/webfiles/ctr/js/post.ts @@ -1,5 +1,5 @@ // Script for the post page view (post.ejs/postPageView.tsx) -import { DELETE } from './xhr'; +import { deletePostById } from './api'; function deletePost(this: HTMLElement, _e: Event): void { var id = this.getAttribute('data-post'); @@ -17,15 +17,16 @@ function deletePost(this: HTMLElement, _e: Event): void { return; } - DELETE('/posts/' + id, function (xhr) { - if (!xhr || xhr.status !== 200) { + deletePostById(id, function (response) { + if (response.status !== 200) { return cave.error_callFreeErrorViewer( 5980030, - 'Post was not able to be deleted. Please try again later.' + `Post was not able to be deleted. Please try again later. (${response.status})` ); } + alert('Post has been deleted.'); - window.location.href = xhr.responseText; + window.location.href = response.nextUrl; }); } From 0c1e2d49f80513088b044286a423d42e902f76f6 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Thu, 22 Jan 2026 19:36:08 +1100 Subject: [PATCH 113/155] feat(portal): bring xhr/empathy/delete improvements to portal too --- apps/juxtaposition-ui/package.json | 3 + .../webfiles/portal/js/api.ts | 53 +++++++++++++++ .../webfiles/portal/js/juxt.js | 67 ++----------------- .../webfiles/portal/js/post.ts | 49 ++++++++++++++ .../webfiles/portal/js/xhr.ts | 39 +++++++++++ .../portal/partials/post_template.ejs | 2 +- .../juxtaposition-ui/webfiles/portal/post.ejs | 2 +- .../webfiles/portal/tsconfig.json | 5 ++ package-lock.json | 21 ++++++ 9 files changed, 178 insertions(+), 63 deletions(-) create mode 100644 apps/juxtaposition-ui/webfiles/portal/js/api.ts create mode 100644 apps/juxtaposition-ui/webfiles/portal/js/post.ts create mode 100644 apps/juxtaposition-ui/webfiles/portal/js/xhr.ts diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 70bd735a..48f283f2 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -59,6 +59,9 @@ "devDependencies": { "@pretendonetwork/cave-types": "^1.0.2", "@pretendonetwork/eslint-config": "^0.1.3", + "@pretendonetwork/wiiu-browser-types": "^1.0.0", + "@pretendonetwork/wiiu-dialog-types": "^1.0.0", + "@pretendonetwork/wiiu-error-viewer-types": "^1.0.0", "@repo/esbuild-plugin-oxipng": "^0.0.0", "@repo/esbuild-plugin-spritesmith": "^0.0.0", "@types/cookie-parser": "^1.4.10", diff --git a/apps/juxtaposition-ui/webfiles/portal/js/api.ts b/apps/juxtaposition-ui/webfiles/portal/js/api.ts new file mode 100644 index 00000000..fcaf8c22 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/portal/js/api.ts @@ -0,0 +1,53 @@ +import { POST, DELETE } from './xhr'; + +export type EmpathyPostResponse = { + status: number; + id: string; + count: number; +}; + +export function empathyPostById(id: string, cb: (post: EmpathyPostResponse) => void): void { + var params = 'postID=' + id; + + POST('/posts/empathy', params, (xhr) => { + if (xhr.status !== 200) { + return cb({ status: xhr.status, id, count: 0 }); + } + var post: EmpathyPostResponse; + + try { + post = JSON.parse(xhr.responseText); + } catch (e) { + console.error(e); + return cb({ status: 400, id, count: 0 }); + } + + cb(post); + }); +} + +export type DeletePostResponse = { + status: number; + nextUrl: string; +}; + +export function deletePostById(id: string, cb: (result: DeletePostResponse) => void): void { + var postUrl = `/posts/${id}`; + + DELETE(postUrl, function (xhr) { + if (xhr.status !== 200) { + return cb({ status: xhr.status, nextUrl: postUrl }); + } + + // HACK: we haven't actually formalised this API serverside yet + // for now, synthesise the response object ourselves + // make sure it looks like a URL... + var nextUrl = xhr.responseText; + if (!/^\/[\w/]+$/.test(nextUrl)) { + return cb({ status: 400, nextUrl: postUrl }); + } + + // make a response object + return cb({ status: 200, nextUrl }); + }); +} diff --git a/apps/juxtaposition-ui/webfiles/portal/js/juxt.js b/apps/juxtaposition-ui/webfiles/portal/js/juxt.js index cc0c30ab..93fd3b10 100644 --- a/apps/juxtaposition-ui/webfiles/portal/js/juxt.js +++ b/apps/juxtaposition-ui/webfiles/portal/js/juxt.js @@ -1,4 +1,7 @@ import Pjax from 'pjax'; +import { GET, POST } from './xhr'; +import { empathyPostById } from './api'; +import { initPostPageView } from './post'; var pjax; setInterval(checkForUpdates, 30000); @@ -36,7 +39,6 @@ function initYeah() { var parent = document.getElementById(id); var count = document.getElementById('count-' + id); el.disabled = true; - var params = 'postID=' + id; if (el.classList.contains('selected')) { el.classList.remove('selected'); parent.classList.remove('yeah'); @@ -53,9 +55,8 @@ function initYeah() { wiiuSound.playSoundByName('SE_WAVE_MII_ADD', 1); } - POST('/posts/empathy', params, function a(data) { - var post = JSON.parse(data.response); - if (!post || post.status !== 200) { + empathyPostById(id, function (post) { + if (post.status !== 200) { // Apparently there was an actual error code for not being able to yeah a post, who knew! // TODO: Find more of these return wiiuErrorViewer.openByCode(1155927); @@ -225,6 +226,7 @@ function initAll() { initPosts(); initMorePosts(); initPostModules(); + initPostPageView(); initSounds(); checkForUpdates(); pjax.refresh(); @@ -358,25 +360,6 @@ function exit() { } window.exit = exit; -function deletePost(post) { - var id = post.getAttribute('data-post'); - if (!id) { - return; - } - var confirm = wiiuDialog.confirm('Are you sure you want to delete your post? This cannot be undone.', 'No', 'Yes'); - if (confirm) { - DELETE('/posts/' + id, function a(data) { - if (!data || data.status !== 200) { - return wiiuErrorViewer.openByCodeAndMessage('5980030', 'Post was not able to be deleted. Please try again later.'); - } - console.log(data); - alert('Post has been deleted.'); - return window.location.href = data.responseText; - }); - } -} -window.deletePost = deletePost; - function reportPost(post) { var id = post.getAttribute('data-post'); var button = document.getElementById('report-launcher'); @@ -422,44 +405,6 @@ function checkForUpdates() { } }); } -function POST(url, data, callback) { - wiiuBrowser.showLoadingIcon(true); - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState === 4) { - wiiuBrowser.showLoadingIcon(false); - return callback(this); - } - }; - xhttp.open('POST', url, true); - xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhttp.send(data); -} -function GET(url, callback) { - wiiuBrowser.showLoadingIcon(true); - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState === 4) { - wiiuBrowser.showLoadingIcon(false); - return callback(this); - } - }; - xhttp.open('GET', url, true); - xhttp.send(); -} - -function DELETE(url, callback) { - wiiuBrowser.showLoadingIcon(true); - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState === 4) { - wiiuBrowser.showLoadingIcon(false); - return callback(this); - } - }; - xhttp.open('DELETE', url, true); - xhttp.send(); -} function back() { if (wiiuBrowser.canHistoryBack()) { diff --git a/apps/juxtaposition-ui/webfiles/portal/js/post.ts b/apps/juxtaposition-ui/webfiles/portal/js/post.ts new file mode 100644 index 00000000..abebbcd8 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/portal/js/post.ts @@ -0,0 +1,49 @@ +// Script for the post page view (post.ejs/postPageView.tsx) +import { deletePostById } from './api'; + +function deletePost(this: HTMLElement, _e: Event): void { + var id = this.getAttribute('data-post'); + if (!id) { + return; + } + + var confirm = wiiuDialog.confirm( + 'Are you sure you want to delete your post? This cannot be undone.', + 'No', + 'Yes' + ); + if (!confirm) { + return; + } + + deletePostById(id, function (response) { + if (response.status !== 200) { + return wiiuErrorViewer.openByCodeAndMessage( + 5980030, + 'Post was not able to be deleted. Please try again later.' + ); + } + + alert('Post has been deleted.'); + window.location.href = response.nextUrl; + }); +} + +function initDeleteButton(): void { + var del = document.querySelector('.button.delete'); + if (!del) { + return; + } + + (del as HTMLElement).addEventListener('click', deletePost); + + // delete buttons from post template + // TODO this is kinda out of place here + document.querySelectorAll('button.remove').forEach((button) => { + (button as HTMLElement).addEventListener('click', deletePost); + }); +} + +export function initPostPageView(): void { + initDeleteButton(); +} diff --git a/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts b/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts new file mode 100644 index 00000000..cd562cc4 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts @@ -0,0 +1,39 @@ +export type XHRCallback = (request: XMLHttpRequest) => void; + +export function POST(url: string, data: string, callback: XHRCallback): void { + wiiuBrowser.showLoadingIcon(true); + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + wiiuBrowser.showLoadingIcon(false); + callback(this); + } + }; + xhttp.open('POST', url, true); + xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhttp.send(data); +} + +export function GET(url: string, callback: XHRCallback): void { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + callback(this); + } + }; + xhttp.open('GET', url, true); + xhttp.send(); +} + +export function DELETE(url: string, callback: XHRCallback): void { + wiiuBrowser.showLoadingIcon(true); + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + wiiuBrowser.showLoadingIcon(false); + callback(this); + } + }; + xhttp.open('GET', url, true); + xhttp.send(); +} diff --git a/apps/juxtaposition-ui/webfiles/portal/partials/post_template.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/post_template.ejs index d910e019..b2888424 100644 --- a/apps/juxtaposition-ui/webfiles/portal/partials/post_template.ejs +++ b/apps/juxtaposition-ui/webfiles/portal/partials/post_template.ejs @@ -63,7 +63,7 @@ <%}%> <% if(reply && post.pid === pid || reply && moderator) {%>
    - +
    <%}%> <%if(!mainPost) {%> diff --git a/apps/juxtaposition-ui/webfiles/portal/post.ejs b/apps/juxtaposition-ui/webfiles/portal/post.ejs index a4abbb12..53030b37 100644 --- a/apps/juxtaposition-ui/webfiles/portal/post.ejs +++ b/apps/juxtaposition-ui/webfiles/portal/post.ejs @@ -20,7 +20,7 @@ Reply <%}%> <% if(post.pid === pid) {%> - Delete Post + Delete Post <%} else {%> Report Post diff --git a/apps/juxtaposition-ui/webfiles/portal/tsconfig.json b/apps/juxtaposition-ui/webfiles/portal/tsconfig.json index f4049aa7..027b5c3b 100644 --- a/apps/juxtaposition-ui/webfiles/portal/tsconfig.json +++ b/apps/juxtaposition-ui/webfiles/portal/tsconfig.json @@ -15,6 +15,11 @@ "noImplicitAny": true, "strictPropertyInitialization": true, "verbatimModuleSyntax": true, + "types": [ + "@pretendonetwork/wiiu-browser-types", + "@pretendonetwork/wiiu-dialog-types", + "@pretendonetwork/wiiu-error-viewer-types" + ], "paths": { "@/*": [ "./*" diff --git a/package-lock.json b/package-lock.json index 5428604e..0b485067 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,9 @@ "devDependencies": { "@pretendonetwork/cave-types": "^1.0.2", "@pretendonetwork/eslint-config": "^0.1.3", + "@pretendonetwork/wiiu-browser-types": "^1.0.0", + "@pretendonetwork/wiiu-dialog-types": "^1.0.0", + "@pretendonetwork/wiiu-error-viewer-types": "^1.0.0", "@repo/esbuild-plugin-oxipng": "^0.0.0", "@repo/esbuild-plugin-spritesmith": "^0.0.0", "@types/cookie-parser": "^1.4.10", @@ -2327,6 +2330,24 @@ "typescript": "^5.7.2" } }, + "node_modules/@pretendonetwork/wiiu-browser-types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pretendonetwork/wiiu-browser-types/-/wiiu-browser-types-1.0.0.tgz", + "integrity": "sha512-P+F51y7rY4E3+Wb72w8sMESFowa1DcwJKxtt5NFlKUyQ4jFCf6sf3ZXMwtdUFzkLyRrjiH6soq5/aYyUMb1wDA==", + "dev": true + }, + "node_modules/@pretendonetwork/wiiu-dialog-types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pretendonetwork/wiiu-dialog-types/-/wiiu-dialog-types-1.0.0.tgz", + "integrity": "sha512-2jPUnIVHPwfDkIL+fKwYL9vlpJfG+tbp8TjJLdNvl5LZ+akcm1zxEgkxVz9qzs9buWOJim7Qnn8ftw7ds7Ljqg==", + "dev": true + }, + "node_modules/@pretendonetwork/wiiu-error-viewer-types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pretendonetwork/wiiu-error-viewer-types/-/wiiu-error-viewer-types-1.0.0.tgz", + "integrity": "sha512-tS6F/B5Og73/n1mqbe3SGuUcNIPpJR6rTrD3rIb4Hf9jROyC6BXteNjuKew8nLq6Xp14hA7QF/1el5n9S4h4fQ==", + "dev": true + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", From 3375ba6a3fbed9283cb9cfc626014998ac4eeddd Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Thu, 22 Jan 2026 19:39:01 +1100 Subject: [PATCH 114/155] fix(ctr): Use cleaner method of importing cave globals --- apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts | 17 ----------------- .../juxtaposition-ui/webfiles/ctr/tsconfig.json | 1 + 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts b/apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts deleted file mode 100644 index bc7c3af9..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/js/cave.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* @pretendonetwork/cave-types DOES ship this in its module, - * but I couldn't work out how to import it nicely... */ -import type CaveAPI from '@pretendonetwork/cave-types/cave'; - -declare global { - interface Window { - /** - * Used by the Miiverse browser applet on the 3DS - */ - cave: CaveAPI; - } - - /** - * Used by the Miiverse browser applet on the 3DS - */ - const cave: CaveAPI; -} diff --git a/apps/juxtaposition-ui/webfiles/ctr/tsconfig.json b/apps/juxtaposition-ui/webfiles/ctr/tsconfig.json index f4049aa7..5bead0e6 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/tsconfig.json +++ b/apps/juxtaposition-ui/webfiles/ctr/tsconfig.json @@ -15,6 +15,7 @@ "noImplicitAny": true, "strictPropertyInitialization": true, "verbatimModuleSyntax": true, + "types": ["@pretendonetwork/cave-types"], "paths": { "@/*": [ "./*" From 025739f0f94ca9ea4e03963185a47c787c08783b Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Thu, 22 Jan 2026 20:07:23 +1100 Subject: [PATCH 115/155] feat(web): bring xhr/empathy/delete improvements to web --- apps/juxtaposition-ui/webfiles/ctr/js/api.ts | 5 +- apps/juxtaposition-ui/webfiles/ctr/js/post.ts | 2 +- .../webfiles/portal/js/api.ts | 5 +- .../webfiles/portal/js/post.ts | 2 +- .../webfiles/portal/js/xhr.ts | 2 +- .../juxtaposition-ui/webfiles/web/css/web.css | 2 +- apps/juxtaposition-ui/webfiles/web/js/api.ts | 54 +++++++++++++++++++ apps/juxtaposition-ui/webfiles/web/js/post.js | 10 ---- apps/juxtaposition-ui/webfiles/web/js/web.js | 50 +++++++---------- apps/juxtaposition-ui/webfiles/web/js/xhr.ts | 35 ++++++++++++ 10 files changed, 118 insertions(+), 49 deletions(-) create mode 100644 apps/juxtaposition-ui/webfiles/web/js/api.ts delete mode 100644 apps/juxtaposition-ui/webfiles/web/js/post.js create mode 100644 apps/juxtaposition-ui/webfiles/web/js/xhr.ts diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/api.ts b/apps/juxtaposition-ui/webfiles/ctr/js/api.ts index fcaf8c22..598cb17b 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/api.ts +++ b/apps/juxtaposition-ui/webfiles/ctr/js/api.ts @@ -31,10 +31,11 @@ export type DeletePostResponse = { nextUrl: string; }; -export function deletePostById(id: string, cb: (result: DeletePostResponse) => void): void { +export function deletePostById(id: string, reason: string | null, cb: (result: DeletePostResponse) => void): void { var postUrl = `/posts/${id}`; + var query = reason !== null ? `?reason=${encodeURIComponent(reason)}` : ''; - DELETE(postUrl, function (xhr) { + DELETE(postUrl + query, function (xhr) { if (xhr.status !== 200) { return cb({ status: xhr.status, nextUrl: postUrl }); } diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/post.ts b/apps/juxtaposition-ui/webfiles/ctr/js/post.ts index 7335a55b..de49b3a9 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/post.ts +++ b/apps/juxtaposition-ui/webfiles/ctr/js/post.ts @@ -17,7 +17,7 @@ function deletePost(this: HTMLElement, _e: Event): void { return; } - deletePostById(id, function (response) { + deletePostById(id, null, function (response) { if (response.status !== 200) { return cave.error_callFreeErrorViewer( 5980030, diff --git a/apps/juxtaposition-ui/webfiles/portal/js/api.ts b/apps/juxtaposition-ui/webfiles/portal/js/api.ts index fcaf8c22..598cb17b 100644 --- a/apps/juxtaposition-ui/webfiles/portal/js/api.ts +++ b/apps/juxtaposition-ui/webfiles/portal/js/api.ts @@ -31,10 +31,11 @@ export type DeletePostResponse = { nextUrl: string; }; -export function deletePostById(id: string, cb: (result: DeletePostResponse) => void): void { +export function deletePostById(id: string, reason: string | null, cb: (result: DeletePostResponse) => void): void { var postUrl = `/posts/${id}`; + var query = reason !== null ? `?reason=${encodeURIComponent(reason)}` : ''; - DELETE(postUrl, function (xhr) { + DELETE(postUrl + query, function (xhr) { if (xhr.status !== 200) { return cb({ status: xhr.status, nextUrl: postUrl }); } diff --git a/apps/juxtaposition-ui/webfiles/portal/js/post.ts b/apps/juxtaposition-ui/webfiles/portal/js/post.ts index abebbcd8..e1c15900 100644 --- a/apps/juxtaposition-ui/webfiles/portal/js/post.ts +++ b/apps/juxtaposition-ui/webfiles/portal/js/post.ts @@ -16,7 +16,7 @@ function deletePost(this: HTMLElement, _e: Event): void { return; } - deletePostById(id, function (response) { + deletePostById(id, null, function (response) { if (response.status !== 200) { return wiiuErrorViewer.openByCodeAndMessage( 5980030, diff --git a/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts b/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts index cd562cc4..3a0fe4e7 100644 --- a/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts +++ b/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts @@ -34,6 +34,6 @@ export function DELETE(url: string, callback: XHRCallback): void { callback(this); } }; - xhttp.open('GET', url, true); + xhttp.open('DELETE', url, true); xhttp.send(); } diff --git a/apps/juxtaposition-ui/webfiles/web/css/web.css b/apps/juxtaposition-ui/webfiles/web/css/web.css index f0717c1c..38109c8e 100644 --- a/apps/juxtaposition-ui/webfiles/web/css/web.css +++ b/apps/juxtaposition-ui/webfiles/web/css/web.css @@ -350,7 +350,7 @@ iframe { color: var(--text); text-align: center; position: fixed; - z-index: 10; + z-index: 1000; left: 50%; top: 100vh; opacity: 0; diff --git a/apps/juxtaposition-ui/webfiles/web/js/api.ts b/apps/juxtaposition-ui/webfiles/web/js/api.ts new file mode 100644 index 00000000..e7cfd1a1 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/web/js/api.ts @@ -0,0 +1,54 @@ +import { POST, DELETE } from './xhr'; + +export type EmpathyPostResponse = { + status: number; + id: string; + count: number; +}; + +export function empathyPostById(id: string, cb: (post: EmpathyPostResponse) => void): void { + const params = 'postID=' + id; + + POST('/posts/empathy', params, (xhr) => { + if (xhr.status !== 200) { + return cb({ status: xhr.status, id, count: 0 }); + } + let post: EmpathyPostResponse; + + try { + post = JSON.parse(xhr.responseText); + } catch (e) { + console.error(e); + return cb({ status: 400, id, count: 0 }); + } + + cb(post); + }); +} + +export type DeletePostResponse = { + status: number; + nextUrl: string; +}; + +export function deletePostById(id: string, reason: string | null, cb: (result: DeletePostResponse) => void): void { + const postUrl = `/posts/${id}`; + const query = reason !== null ? `?reason=${encodeURIComponent(reason)}` : ''; + + DELETE(postUrl + query, function (xhr) { + if (xhr.status !== 200) { + return cb({ status: xhr.status, nextUrl: postUrl }); + } + + // HACK: we haven't actually formalised this API serverside yet + // for now, synthesise the response object ourselves + // make sure it looks like a URL... + const nextUrl = xhr.responseText; + if (!/^\/[\w/]+$/.test(nextUrl)) { + return cb({ status: 400, nextUrl: postUrl }); + } + + // make a response object + return cb({ status: 200, nextUrl }); + }); +} diff --git a/apps/juxtaposition-ui/webfiles/web/js/post.js b/apps/juxtaposition-ui/webfiles/web/js/post.js deleted file mode 100644 index 3f1ca97c..00000000 --- a/apps/juxtaposition-ui/webfiles/web/js/post.js +++ /dev/null @@ -1,10 +0,0 @@ -export async function deletePost(id, reason) { - if (!id) { - return; - } - - const params = new URLSearchParams({ reason }); - - await fetch(`/posts/${id}?${params}`, { method: 'DELETE' }); - location.reload(); -} diff --git a/apps/juxtaposition-ui/webfiles/web/js/web.js b/apps/juxtaposition-ui/webfiles/web/js/web.js index 1d774c14..b6ec292c 100644 --- a/apps/juxtaposition-ui/webfiles/web/js/web.js +++ b/apps/juxtaposition-ui/webfiles/web/js/web.js @@ -1,7 +1,8 @@ import Pjax from 'pjax'; import { popupItemCb, setupPopup } from './menus'; import { initReportForm, reportPost } from './reports'; -import { deletePost } from './post'; +import { POST, GET } from './xhr'; +import { deletePostById, empathyPostById } from './api'; let pjax; setInterval(checkForUpdates, 30000); @@ -38,7 +39,6 @@ function initYeah() { const parent = document.getElementById(id); const count = document.getElementById('count-' + id); el.disabled = true; - const params = 'postID=' + id; if (el.classList.contains('selected')) { el.classList.remove('selected'); parent.classList.remove('yeah'); @@ -49,12 +49,9 @@ function initYeah() { count.innerText = ++count.innerText; } - POST('/posts/empathy', params, function a(data) { - const post = JSON.parse(data.response); - if (!post || post.status !== 200) { - // Apparently there was an actual error code for not being able to yeah a post, who knew! - // TODO: Find more of these - Toast(1155927); + empathyPostById(id, function (post) { + if (post.status !== 200) { + return Toast('You cannot give this post a Yeah!'); } el.disabled = false; count.innerText = post.count; @@ -104,17 +101,29 @@ function initPopupMenus() { popupItemCb(menu.querySelector('[data-action="report"]'), (_item, _ev) => { reportPost(post); }); - popupItemCb(menu.querySelector('[data-action="delete"]'), (item, _ev) => { + popupItemCb(menu.querySelector('[data-action="delete"]'), async (item, _ev) => { const moderator = item.getAttribute('data-moderator'); let reason = ''; if (moderator === 'true') { reason = prompt('Provide explanation for removing post:'); + } else { + reason = + confirm('Are you sure you want to delete your post? This cannot be undone.') + ? '' + : null; } if (reason == null) { return; // User canceled } - deletePost(post, reason); + deletePostById(post, reason, (result) => { + if (result.status !== 200) { + return alert(`Post was not able to be deleted. Please try again later. (${result.status})`); + } + + alert('Post has been deleted'); + window.location.href = result.nextUrl; + }); }); popupItemCb(menu.querySelector('[data-action="copy"]'), (_item, _ev) => { copyToClipboard(`${window.location.origin}/posts/${post}`); @@ -310,27 +319,6 @@ function checkForUpdates() { xhttp.open('GET', '/users/notifications.json', true); xhttp.send(); } -function POST(url, data, callback) { - const xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState === 4) { - return callback(this); - } - }; - xhttp.open('POST', url, true); - xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhttp.send(data); -} -function GET(url, callback) { - const xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function () { - if (this.readyState === 4) { - return callback(this); - } - }; - xhttp.open('GET', url, true); - xhttp.send(); -} window.onscroll = function (_ev) { if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight && document.getElementById('load-more')) { diff --git a/apps/juxtaposition-ui/webfiles/web/js/xhr.ts b/apps/juxtaposition-ui/webfiles/web/js/xhr.ts new file mode 100644 index 00000000..3d7bd4ce --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/web/js/xhr.ts @@ -0,0 +1,35 @@ +export type XHRCallback = (request: XMLHttpRequest) => void; + +export function POST(url: string, data: string, callback: XHRCallback): void { + const xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + callback(this); + } + }; + xhttp.open('POST', url, true); + xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhttp.send(data); +} + +export function GET(url: string, callback: XHRCallback): void { + const xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + callback(this); + } + }; + xhttp.open('GET', url, true); + xhttp.send(); +} + +export function DELETE(url: string, callback: XHRCallback): void { + const xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function (): void { + if (this.readyState === 4) { + callback(this); + } + }; + xhttp.open('DELETE', url, true); + xhttp.send(); +} From c13fcb0a06735e12f41390f453e94bad7744dcf8 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Thu, 22 Jan 2026 20:13:58 +1100 Subject: [PATCH 116/155] fix(ui): use onload for xhr this works fine on ctr and is a bit neater --- apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts | 22 +++++++------------ .../webfiles/portal/js/xhr.ts | 22 +++++++------------ apps/juxtaposition-ui/webfiles/web/js/xhr.ts | 18 +++++---------- 3 files changed, 22 insertions(+), 40 deletions(-) diff --git a/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts b/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts index da8309f1..165c8b2f 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts +++ b/apps/juxtaposition-ui/webfiles/ctr/js/xhr.ts @@ -6,11 +6,9 @@ export type XHRCallback = (request: XMLHttpRequest) => void; export function POST(url: string, data: string, callback: XHRCallback): void { cave.transition_begin(); var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - cave.transition_end(); - callback(this); - } + xhttp.onload = function (): void { + cave.transition_end(); + callback(this); }; xhttp.open('POST', url, true); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); @@ -19,10 +17,8 @@ export function POST(url: string, data: string, callback: XHRCallback): void { export function GET(url: string, callback: XHRCallback): void { var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - callback(this); - } + xhttp.onload = function (): void { + callback(this); }; xhttp.open('GET', url, true); xhttp.send(); @@ -31,11 +27,9 @@ export function GET(url: string, callback: XHRCallback): void { export function DELETE(url: string, callback: XHRCallback): void { cave.transition_begin(); var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - cave.transition_end(); - callback(this); - } + xhttp.onload = function (): void { + cave.transition_end(); + callback(this); }; xhttp.open('POST', url, true); xhttp.setRequestHeader('X-HTTP-Method-Override', 'DELETE'); diff --git a/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts b/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts index 3a0fe4e7..22c1d6f1 100644 --- a/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts +++ b/apps/juxtaposition-ui/webfiles/portal/js/xhr.ts @@ -3,11 +3,9 @@ export type XHRCallback = (request: XMLHttpRequest) => void; export function POST(url: string, data: string, callback: XHRCallback): void { wiiuBrowser.showLoadingIcon(true); var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - wiiuBrowser.showLoadingIcon(false); - callback(this); - } + xhttp.onload = function (): void { + wiiuBrowser.showLoadingIcon(false); + callback(this); }; xhttp.open('POST', url, true); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); @@ -16,10 +14,8 @@ export function POST(url: string, data: string, callback: XHRCallback): void { export function GET(url: string, callback: XHRCallback): void { var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - callback(this); - } + xhttp.onload = function (): void { + callback(this); }; xhttp.open('GET', url, true); xhttp.send(); @@ -28,11 +24,9 @@ export function GET(url: string, callback: XHRCallback): void { export function DELETE(url: string, callback: XHRCallback): void { wiiuBrowser.showLoadingIcon(true); var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - wiiuBrowser.showLoadingIcon(false); - callback(this); - } + xhttp.onload = function (): void { + wiiuBrowser.showLoadingIcon(false); + callback(this); }; xhttp.open('DELETE', url, true); xhttp.send(); diff --git a/apps/juxtaposition-ui/webfiles/web/js/xhr.ts b/apps/juxtaposition-ui/webfiles/web/js/xhr.ts index 3d7bd4ce..f89b98a8 100644 --- a/apps/juxtaposition-ui/webfiles/web/js/xhr.ts +++ b/apps/juxtaposition-ui/webfiles/web/js/xhr.ts @@ -2,10 +2,8 @@ export type XHRCallback = (request: XMLHttpRequest) => void; export function POST(url: string, data: string, callback: XHRCallback): void { const xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - callback(this); - } + xhttp.onload = function (): void { + callback(this); }; xhttp.open('POST', url, true); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); @@ -14,10 +12,8 @@ export function POST(url: string, data: string, callback: XHRCallback): void { export function GET(url: string, callback: XHRCallback): void { const xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - callback(this); - } + xhttp.onload = function (): void { + callback(this); }; xhttp.open('GET', url, true); xhttp.send(); @@ -25,10 +21,8 @@ export function GET(url: string, callback: XHRCallback): void { export function DELETE(url: string, callback: XHRCallback): void { const xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function (): void { - if (this.readyState === 4) { - callback(this); - } + xhttp.onload = function (): void { + callback(this); }; xhttp.open('DELETE', url, true); xhttp.send(); From 6c74cede347234cb34dc02f6925f80068a2bee7b Mon Sep 17 00:00:00 2001 From: mrjvs Date: Fri, 23 Jan 2026 16:50:06 +0100 Subject: [PATCH 117/155] fix: error view not being centered --- .../src/services/juxt-web/views/web/errorView.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx index 8f432279..90c2d08d 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx @@ -1,5 +1,4 @@ import { WebLoginRoot } from '@/services/juxt-web/views/web/login'; -import { WebWrapper } from '@/services/juxt-web/views/web/root'; import { PretendoLogo } from '@/services/juxt-web/views/web/icons'; import type { ReactNode } from 'react'; import type { ReqId } from 'pino-http'; @@ -22,7 +21,7 @@ export function WebErrorView(props: ErrorViewProps): ReactNode { return ( - + ); } From e8053245fe76a128a4cbbf2ecaa45f931eb2e031 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Fri, 23 Jan 2026 17:35:08 +0100 Subject: [PATCH 118/155] fix: proper body parsing for post creation --- .../src/services/juxt-web/routes/console/posts.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx index 07750cf6..6ba22e3d 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx @@ -308,16 +308,16 @@ async function newPost(req: Request, res: Response): Promise { post_id: z.string().optional() }), body: z.object({ - body: z.string(), - painting: z.string(), - screenshot: z.string(), + body: z.string().default(''), + painting: z.string().default(''), + screenshot: z.string().default(''), community_id: z.string(), _post_type: z.string(), - bmp: z.stringbool(), + bmp: z.stringbool().default(false), feeling_id: z.coerce.number(), - spoiler: z.boolean(), - is_app_jumpable: z.boolean(), - language_id: z.coerce.number() + spoiler: z.stringbool().default(false), + is_app_jumpable: z.stringbool().default(false), + language_id: z.coerce.number().optional() }) }); From f8f9f9371c0651f6671488a6e115750cf3fb8ef7 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Fri, 23 Jan 2026 17:45:16 +0100 Subject: [PATCH 119/155] fix: allow quests to view posts + fix userContent not being used --- .../juxt-web/routes/console/posts.tsx | 33 +++++++++++-------- .../juxt-web/views/ctr/postPageView.tsx | 4 +-- .../juxt-web/views/portal/postPageView.tsx | 4 +-- .../src/services/juxt-web/views/web/post.tsx | 2 +- .../juxt-web/views/web/postPageView.tsx | 6 ++-- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx index 6ba22e3d..be93345c 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx @@ -25,6 +25,7 @@ import type { PostPageViewProps } from '@/services/juxt-web/views/web/postPageVi import type { PostSchema } from '@/models/post'; import type { CommunitySchema } from '@/models/communities'; import type { HydratedSettingsDocument } from '@/models/settings'; +import type { ContentSchema } from '@/models/content'; const upload = multer({ dest: 'uploads/' }); export const postsRouter = express.Router(); @@ -59,13 +60,14 @@ const yeahLimit = rateLimit({ }); postsRouter.get('/:post_id/oembed.json', async function (req, res) { - const { params, auth } = parseReq(req, { + const { params, auth, hasAuth } = parseReq(req, { params: z.object({ post_id: z.string() }) }); + const maybeTokens = hasAuth() ? auth().tokens : {}; - const post = await getPostById(auth().tokens, params.post_id); + const post = await getPostById(maybeTokens, params.post_id); if (!post) { return res.sendStatus(404); } @@ -137,38 +139,43 @@ postsRouter.post('/new', postLimit, upload.none(), async function (req, res) { }); postsRouter.get('/:post_id', async function (req, res) { - const { params, auth } = parseReq(req, { + const { params, hasAuth, auth } = parseReq(req, { params: z.object({ post_id: z.string() }) }); + const maybeTokens = hasAuth() ? auth().tokens : {}; - const userSettings = await database.getUserSettings(auth().pid); - const userContent = await database.getUserContent(auth().pid); + let userSettings: HydratedSettingsDocument | null = null; + let userContent: InferSchemaType | null = null; + if (hasAuth()) { + userSettings = await database.getUserSettings(auth().pid); + userContent = await database.getUserContent(auth().pid); + } - const post = await getPostById(auth().tokens, params.post_id); + const post = await getPostById(maybeTokens, params.post_id); if (!post) { return res.redirect('/404'); } if (post.parent) { - const parent = await getPostById(auth().tokens, post.parent); + const parent = await getPostById(maybeTokens, post.parent); if (!parent) { return res.redirect('/404'); } return res.redirect(`/posts/${parent.id}`); } const community = await database.getCommunityByID(post.community_id); - if (!community || !userSettings || !userContent) { + if (!community) { return res.redirect('/404'); } - const replies = (await getPostsByParentId(auth().tokens, post.id, 0))?.items ?? []; + const replies = (await getPostsByParentId(maybeTokens, post.id, 0))?.items ?? []; const postPNID = await getUserAccountData(post.pid); - const canPost = ( + const canPost = hasAuth() && ( (community.permissions.open && community.type < 2) || (community.admins && community.admins.indexOf(auth().pid) !== -1) || (auth().user.accessLevel >= community.permissions.minimum_new_comment_access_level) - ) && userSettings.account_status === 0; + ) && userSettings?.account_status === 0; const props: PostPageViewProps = { ctx: buildContext(res), @@ -176,8 +183,8 @@ postsRouter.get('/:post_id', async function (req, res) { post, postPNID, replies, - userContent, - canPost + canPost, + userContent }; res.jsxForDirectory({ web: , diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/postPageView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/postPageView.tsx index b2caf239..05df5a3f 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/postPageView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/postPageView.tsx @@ -43,10 +43,10 @@ export function CtrPostPageView(props: PostPageViewProps): ReactNode {
    - +
    {props.replies.map(replyPost => ( - + ))}
    {props.canPost ? : null} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/postPageView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/postPageView.tsx index a203ed21..196ff89e 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/postPageView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/postPageView.tsx @@ -37,10 +37,10 @@ export function PortalPostPageView(props: PostPageViewProps): ReactNode {
    - +
    {props.replies.map(replyPost => ( - + ))}
    {props.canPost ? : null} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/post.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/post.tsx index 3c1d7341..5766c945 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/post.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/post.tsx @@ -11,7 +11,7 @@ import type { PostDto } from '@/api/post'; export type PostViewProps = { ctx: RenderContext; - userContent?: InferSchemaType; + userContent?: InferSchemaType | null; post: InferSchemaType | PostDto; isReply?: boolean; isMainPost?: boolean; diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/postPageView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/postPageView.tsx index b586d68a..1835ab77 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/postPageView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/postPageView.tsx @@ -14,7 +14,7 @@ import type { PostDto } from '@/api/post'; export type PostPageViewProps = { ctx: RenderContext; post: PostDto; - userContent: InferSchemaType; + userContent: InferSchemaType | null; postPNID: GetUserDataResponse; community: InferSchemaType; replies: PostDto[]; @@ -77,11 +77,11 @@ export function WebPostPageView(props: PostPageViewProps): ReactNode {
    - + {props.replies.map(replyPost => (
    - +
    ))} From 3a51b8523b256780bfdf18c786b594bf1d793dfa Mon Sep 17 00:00:00 2001 From: mrjvs Date: Fri, 23 Jan 2026 17:50:43 +0100 Subject: [PATCH 120/155] fix: fixed styling of the yeah button on web --- apps/juxtaposition-ui/webfiles/web/css/web.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/juxtaposition-ui/webfiles/web/css/web.css b/apps/juxtaposition-ui/webfiles/web/css/web.css index f0717c1c..e793beef 100644 --- a/apps/juxtaposition-ui/webfiles/web/css/web.css +++ b/apps/juxtaposition-ui/webfiles/web/css/web.css @@ -284,7 +284,7 @@ header > .selected > svg { } /* Red fill for .selected icons (Yeah'd post) */ -.post-buttons-wrapper > span.selected > svg { +.post-buttons-wrapper > span.selected svg { fill: red; stroke: red; } From 2b653398dcf144cc72c036528a8cee424afc1855 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Fri, 23 Jan 2026 21:12:46 +0100 Subject: [PATCH 121/155] fix: solve crashes with metrics server on an empty database --- apps/juxtaposition-ui/src/metrics.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/juxtaposition-ui/src/metrics.ts b/apps/juxtaposition-ui/src/metrics.ts index e32ac302..c88dc9b2 100644 --- a/apps/juxtaposition-ui/src/metrics.ts +++ b/apps/juxtaposition-ui/src/metrics.ts @@ -4,7 +4,7 @@ import express from 'express'; import { logger } from '@/logger'; import { config } from '@/config'; import { SETTINGS } from '@/models/settings'; -import type { Express } from 'express'; +import type { Express, NextFunction, Request, Response } from 'express'; export const onlineNowGauge = new Gauge({ name: 'juxtaposition_online_users_now', @@ -12,11 +12,11 @@ export const onlineNowGauge = new Gauge({ async collect(): Promise { const onlineRangeMs = 10 * 60 * 1000; // 10 minutes const cutoff = new Date(Date.now() - onlineRangeMs); - const [{ n }] = await SETTINGS.aggregate<{ n: number }>([ + const [result] = await SETTINGS.aggregate<{ n: number } | undefined>([ { $match: { last_active: { $gt: cutoff } } }, { $count: 'n' } ]); - this.set(n); + this.set(result?.n ?? 0); } }); @@ -26,11 +26,11 @@ export const activeMonthlyGauge = new Gauge({ async collect(): Promise { const monthlyRangeMs = 30 * 24 * 60 * 60 * 1000; const cutoff = new Date(Date.now() - monthlyRangeMs); - const [{ n }] = await SETTINGS.aggregate<{ n: number }>([ + const [result] = await SETTINGS.aggregate<{ n: number } | undefined>([ { $match: { last_active: { $gt: cutoff } } }, { $count: 'n' } ]); - this.set(n); + this.set(result?.n ?? 0); } }); @@ -40,11 +40,11 @@ export const activeYearlyGauge = new Gauge({ async collect(): Promise { const yearlyRangeMs = 365 * 24 * 60 * 60 * 1000; const cutoff = new Date(Date.now() - yearlyRangeMs); - const [{ n }] = await SETTINGS.aggregate<{ n: number }>([ + const [result] = await SETTINGS.aggregate<{ n: number } | undefined>([ { $match: { last_active: { $gt: cutoff } } }, { $count: 'n' } ]); - this.set(n); + this.set(result?.n ?? 0); } }); @@ -87,6 +87,11 @@ export function registerMetrics(app: Express): Express { })); } + metrics.use((error: Error, req: Request, res: Response, _next: NextFunction) => { + logger.error(error, 'Request failed!'); + res.sendStatus(500); + }); + return metrics; } From 7af54b0d361cef0c599cdc09053312e7337ed31d Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Mon, 26 Jan 2026 17:20:35 +1100 Subject: [PATCH 122/155] fix(ui): use onload for scroll to bottom event the old method was broken after TSX since it strips the empty src= attribute, and this is just cleaner anwyay --- .../src/services/juxt-web/views/ctr/messageThread.tsx | 3 +-- .../src/services/juxt-web/views/portal/messageThread.tsx | 3 +-- .../src/services/juxt-web/views/web/messageThread.tsx | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/messageThread.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/messageThread.tsx index 8f8ec9ec..569b8f28 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/messageThread.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/messageThread.tsx @@ -51,7 +51,7 @@ export function CtrMessageThreadView(props: MessageThreadViewProps): ReactNode { const otherUserName = props.ctx.usersMap.get(props.otherUser.pid) ?? ''; return ( - +
    - ); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/messageThread.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/messageThread.tsx index 549c9768..3df86975 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/messageThread.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/messageThread.tsx @@ -52,7 +52,7 @@ export function PortalMessageThreadView(props: MessageThreadViewProps): ReactNod const otherUserName = props.ctx.usersMap.get(props.otherUser.pid) ?? ''; return ( - +
  • - +
  • - + input { @@ -682,7 +682,7 @@ menu.tab-header.no-margin { .textarea-menu { height: 123px; - margin-top: 10px; + margin-top: 8px; position: relative; margin-left: 2px; } @@ -766,33 +766,18 @@ menu.tab-header.no-margin { display: inline-block } -.textarea-menu .sprite { +.textarea-menu .sprite-text-input { position: absolute; - left: 9px; - width: 26px; - height: 17px; - background-size: 170px; - pointer-events: none -} - -.sprite.text-input { + pointer-events: none; top: 24px; - background-position: -61px -86px -} - -.sprite.text-input.selected { - background-position: -86px -86px + left: 9px; } -.sprite.memo { +.textarea-menu .sprite-memo-input { + position: absolute; + pointer-events: none; top: 85px; - background-position: -65px -105px; - width: 17px; - left: 14px -} - -.sprite.memo.selected { - background-position: -85px -105px; + left: 13px } .checkbox-container span { diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css index 810c1b49..abddc127 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css @@ -94,13 +94,13 @@ To change `display` (e.g. `display: inline-block;`), we suggest using a common C width: 19px; height: 14px; } -.sprite-memo-input-dark { +.sprite-memo-input { background-image: url(/images/sprites.png); background-position: -112px -38px; width: 17px; height: 17px; } -.sprite-memo-input-light { +.sprite-memo-input.selected { background-image: url(/images/sprites.png); background-position: -112px -55px; width: 17px; @@ -130,13 +130,13 @@ To change `display` (e.g. `display: inline-block;`), we suggest using a common C width: 12px; height: 12px; } -.sprite-text-input-dark { +.sprite-text-input { background-image: url(/images/sprites.png); background-position: -26px -84px; width: 26px; height: 17px; } -.sprite-text-input-light { +.sprite-text-input.selected { background-image: url(/images/sprites.png); background-position: -52px -84px; width: 26px; diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png index 9de5321cecc09a9185e8a9548f7a05a96fca1d58..22de31be42bd557ee1fce851f68254e2fd821462 100644 GIT binary patch literal 14638 zcmbW81z23omaa(%1PJaOrmY|I`!QC}La0#vr1b5daKyY`tn_te% znS0K?GxI#qy?d?NRYmQp>h*rB2z{p{jeJ<_?skX_s`mgWFrL&E(_|g15`?Av`z*3NIuT$IEgH@@`{r?b59$P@JZ0 z<87QD(|6~ab^MeUGEAnc?Chjj<~!fpEdBgh%KR#5yVQQuIw!8=GaUnY-vaU=>*?U( zsb+@@eun7Voz_@bbc`-mIB8=B$h;law4u;+ew-^m^24bb8`F6z1)nPWdwiREMJ+5N zAFAEuw8C`M01$pKJujvj+q8XZ1^jO1oBWyyqod5sFr(rX?EbGY#hqGBcAY5*yOi3O zn}Yji0ttK45^-!~csa0keS2l?@TCxDtu)mgX9Us)?w{K1|KyO=y4$n{TogGG*A3_- zmg43PaNLGm_8seSs+rf5tP2DjZyI@ad(%BsG10=d%VD zuqubMe^AoFYH9bBS;F9;+8T_61fp{V`UFW@d;_7ps$3APqXB8Y`R0pPY)c*5p(O)M z$;pMCuX+5gDw)nkD1At~_L+||>DL4Fv9)hf92sJsVSeZ^0NFbAzs6AMaA_dLB5CFx z?AaC^Nz^QvO1XF6R#Tx5+I1A7;di%7(q+A=sy_V{o}giKK}o^TfI4H66EG9}13!K1 z`oy|crq=?#LIFN=T}k&uCA@#W8>CCFo*nWl!fcN-Hy+{ynT(yM$XG~ zg@r+j8T$4$x)>*>gYr5pHLQdL+>FUHLXt+Qv?vUZ43-j&5-yiJcCcLFZY4}4>IX_C zWutC0i>(1%=CbKY3=AASFr4M5Z9*r0sSS_TQXRl}JRrz`@xAvsC;z8NUZEh2#PoC) z-I^Mm>fR=$hIRfmb)a>0gcqG!Mx}pTJkOSuaPB9?Q0cUA#EF?lmVh_gygmgRK$Ps|DFO0d ziG>jpp~L~494`|o_`EUS972h2|Bc;C0wbPI44XU*T@pkgfsuot68L|A%%;nU@lcV7 zt3+o;ZPQ?i096S+P2&!+8s0md%A@5Vl{#Ry|9kI64qcdmG&ThiMi59$f;$=nf7}t zT!u-QVdfjTzDns;Bef#?^_3GW?XGQwA1^BdpE9;+m7xh#*e1lmIzkGpx(O8O=DG?I zjue5R(;U#%7G2K2|D*pLk}$gUOf?DQt`F|1Lepi-JGutG-GL^-O{+Ojp%&|KGQU$q zC{4{(J5Dw7rv;R@n&TfN!#*oS3cvAxdDWMmEykuVd;M03EhI7eoVnH<@(uH#bD^2a zG9AKpm~X+M>>G*E%eH3g+R}~{zW)maZ-O&;>%i!?oNrT{6TI2)Fjh<0&*+=rv>ZAk z*YL_gO}b2D`(TIT4n$KvG2*x^{vOe7&2GmMU+R>%F+-||WsE;4BXZJ}@aG8o@NLge z1BAI5iDshyY(#n=9=UupE<<*H62wgV9R++H3Vk~!08R0TvT{$`4?`pBX&{;wu1g_8=!U-wJ)O=fU)XgPlC z-Hc>8QTcp0vlC~3Cyh}sTP9A@(sZ+QgNCNlRyUD+C=AhEaf*oBGdnNm(LUdP6_|)Z zwzLsfuQ)!rX!vLClVhxDJo*X~boyEyjRK%feO1VY8;3bfQ{B;(v32^+Z@SJn`8K}W zHhb^G`^3CaYz)888>

    zooOUs=UMX#vON)KI`mNA91K_O))eGMWEDRLDqO5ntFbIL9}<^P<|Zl zm2n(yHgeNbWx?F$T(+;cXCndrMb4%HKbbA~nWOkezY- zm2W&;_@r!3_pCRlGrckugpNTWCILdgAg7QB1fjEF9y??=98s=#4w$o8x2v1N&&^Gh zSV(6gs2WRKl(#PXMk-Rno?l$-?kxVI#yI;lwYRtPUOLzcQ_MX21j7{NLDvIc8|!}Q zB8F>hbiK{Yj8M9&OSlTVV2)X>_e!>Fh#x95BC=pMZM9FBDhJ=R+SRLldu3jZ&-ZPv zh>dxtk5y5u4>@7BHEdsbTrgz_th!qR)}c++WH+Y~N}X#(k`>ZMnmAHpX}oK4nEMEA zI!FyEhd&fM(TO=^9Bf(_D0HZkhJ_HAc5$S)p2o*Fx=ktw_{IS%yG;AXlyd8RgPWgw z@E6~Yw@{7pS>v-SbLog;trwyMh)mAK*Cs)x(MGXJCokhtwmriN#*l3+ zehO&&l(Z=DWhNk=xm4M3lu5H)3%c4FZ*jmAg_|O1C)_>UMolcJsaesNZ1u@@k?>hH zz;bHH1iw2w*|6iR8Eu-1_H{-}CCjS|$eDSF;A)pX;Pn}JY*}p$7xxZI3OBWM%cB@- zTOVFou>CxU9t^hr^iz9(*{_G`@a6~^tDue{o+BXsXELIiZi~$_@?o2Cg4(b(kGFbm z!24bEfpTpZV1b8c3}JKK(pjI`0?WB_$Gq+?MF)HnGo#Kx9iLFO5}%W}%#0xZ%Cr96 z*?gNR8QJ~vZ{1tUH=z`*B$5*U*q5_GhXszu(ua2~L-1DZQUOn6^78SHD&ELUJ3%3d zz4N8;#~4*{^NQ1lE3NxlvHbi!>cb?j(t~s#9cj*DlUyo9ueobv(|ky|NZ;GxpxTM2;G!E|8&_e*WV&P zeUr!{WO|!GnjfqH+~I<)MOi9p6y0hk5n15We2h6iA~ibta}tg1T%5Cp!cruaM_+7$ zH%DrP`y9K2uv9cV>Rj2Oe%rzC;8;5IQis&<%p?5aVryEYWeFXr6a(l;;2-tS7`&a9 z5oc5r>7GOz8f1x^lwQ`4DT#`Li5YnK&J^?}KjBIrEW%Pe zx$`PW5s60aqnT41?Yvz>4p&Z+F79{ziy}byGqW68B2$egvEA+*Zb@<-Xx$Wq9!c z+}8HVS5%Ci9q+LRZMk*HmvhysRI2IO&Fw4sL+{RSIH|Q!Obc`GooDQnK?uO!!E)Ur zG$Kd;)sjTJ&br6^sn8r=vuJS~fho>(yc~AWlqJtwYr}E48l{|R#o|yz3ksDpkGbt+ z1d1Y;3I0%7yov)ai6O%h)9E=ou6dM?i?VhZymq@HtfVdN-+0L? zyJZ&6*dE6^l#aLLnnQluMF|uSp=%g@D3(ysigbm|gQxlMc9+U}DJ~_+kp@#TGchtF z^tHbZPg!izr)RsZ(v!$_a^~qXj=#Xm%b}P2Z}WlZaNx8#;uNLA1BxngbAv@lD8KnI zcPv%QrF}cAk*|6pjn^_pWFB?g9fpT3$qV(*9rV351(&;bIArXc6vXxtOwU_JD;LHc zv^iPTx?XF05Nr`;{j#LVkQ^InBvaWfFng|2;mh|ynd6R56;co~d-j^LK$Spxi~pu> zR-P(T_}Ba=zgnHLUmp69Yj|0Dz8jR>Pp%j42mMN5p_+ayux-`O3{jc)5zm3aO)1Xf z15s^j=io%Iz+p=-L!x}}(eJqNd!9FC({Dr=S)ksqHN->R)pR%7mz35^H43khrdbMi zt%AuUf6ktvr;?g>t202i^`uvDy+Su83iVPC+>*Fv?bvWv-M!R<<4JhDN@b5aW~i6#}c zj6M#qLsFF^nIgct7#;L`=&VcP`JMS$yoD!UfYN8k#*0pk)sZ+!3IfHeGwjfw@4%1d59evg?P8M{Fx+-q;w zo~g4pdU9-XF{}7ThbY1#$O~E3%GJ#cEO~_=llUU0g}PksA^{y9X|bqB=t9QY;Y}yd zfmY1r2!R%+<-AsnyB0c)xIL9J4a$+zU-)0=%_{x3=bz3vOcf= z+AREq#WdMQ=|*CQL#9?I5@0uz7v_{6KFx#;tS1H7$dx!wo}7-V!J3`(8cyl9O! z`_A_pt9XR8T8ZzJAEPkrwLby{=;;L#hR^u-HOP`8MIOFLqofw7G20(&bbIvET6hCZ z{-g=Q69PV=mHa>K+M6lbe(9db*#L!|2pR+A9*0l*+*O+(DX|snqw7*^!*Y$y181~?(NN&Ke@&AM;AOaTB)w! zl*Cd4s-K_MUY*Eh%J{(Z9dB?5i`M*2XMaQ5vZ)9_`W_7kB>3RLWhC2nhQ)|fRXMJZ zRRE;y4>*#qsE^bN zIM+&4-EqYijFYBLB9;{>K!P??&4IY+OEs$wHk1qOLqsLiSqb)&!kku!?j~)rB7YWzVqKVUF?m!Jk1`ZF4n|VZjVO`b5xOL);twH9nG`W z8=gI>6nKD2vDYh}874=s7qZJX)?w4N=s)(fYz&?h39^5sxbSOyZz#98iI_M*I;L6I zqQLlTHm=YL-<9d&+Sf_<0|G?Hxz=Z?knb3UCK4H21K*~s6-n9#b1~OEnSez%1Da67 zcbc2Ku9B4hpX(?v&n@^)Z;{y>xvkvFEAv~MS+(FN?E*>huXFk+s`pVfH*a> z%0N|a&}pjl5J=P|MQ1En|~DA>m_+SLDRj>eT<<9 zTL=fF1Tl;tiZHsID|-p2>9rMhx(=&3$UbVvb2kpza^=1BabN|);w0Kw4r$8ykz0pbb7bFa*2zY>Qx8xCTa%0vJ^8~}sWC%QT}&05 zorL4LGukpO{4B|&)A~TUUzb4UPXhg3R$Xdqab+NK6v`gKjMrIbBmvoe@n znEgpsD2Up0QlD?Oy;84|y0!FUG%;t}lz^nkEa+O=YmWS>R1g?W&W2s;u}>6HGS=De zOh3ily7OtrArr{1n`BDPorjS#fNi(HJ{Q8{HA2l1L8!~t=T7{)-BjyyPm-w!AuCQY zKSY_(ewskq^I`5ywUW)utU9}viCTZCgVjuZiNQj*29&mzQ0z%_D2O`Pn!R~3WHNil z+Vh6Wi{7=;1J1;JtUC1IJUAx*NlTaGo%<@1^B`cT>GG$R->hI&ZacHU=ZP9p= z67~`ZOf59aGe~?WWI{>OTW&mWxwzwzCl?mVOPmuEB}OOSS67#4k`NTE6<(A-%j|kt zfqJ>>XBfaw$U2N(#dLjtNm5u}SO8#KTymk(D4S0jWD{OHVXs$Q;X&$@IQ;I(#@nhP~go=q=N)?AbmZio#c`+|<1iHDwVjr zj0|8ywz?{kDomR?xYry!`HnWiYGhra%EzHqoR-X5JJqx~bo9Vz&iAX-wM&|d{{_JV zuCvPP3Rve#!bh}Nl=07zzHZ!dF-J{RDlvg8@sTp{PhCQLxRzz;`dP)4%xV^h=Dk4$ zFudW`{c+`uO^}@YocuD|rPh+bZB3M=YOmCJ{IT{O;&6B7dapaJZ`fm~*%kI1?zh2Q zbd!}2zxQ$P_eX>twomm3pkQH#v6Y9#o5Fe`u`gVmgxK(}bfVT9!0b2Jf!T1m%4-ea z@sHSILXs6yS^=Ggq5~?#mnu@3{;V~IXY|0OPLxIpeC+f6g;m zK+VAUhkRgSKp-3*?)Z$fvwY0|5L%WL+V9@?nqb-_fWOXmr|x);q}6Z0BtyTp=iOge zJqWRIo2@EdK<75}2|S<8iYidy1e=T#OA&`xlpA(eDx`>?@}@`;{0zpPmevBJ>{t>? zp7PQR8Fg18+#*R459IN2f^)6nvH{E%0@_(=MbUC_Ab5bsTlcvy z@h(@CT`+QLhxZ_ln?uQFV(I;9nIzcr?8|wb-S4`RCUZ@dKOQK%cvBB z!g@ZSu((!`OMXmY<-B1iGg`GBPQH1RFPfdj_@q>EvMPt7T}^ub*kShMVBLFL4}4i6 zfVJc0G9cCg)OCV@EiQ%cMyMuTR*fj%TyBW7cFdJVx-CSNV?m?xml4%@X^eIVP_^ij z){Q#OP>93``ME^Y?{`>NBs9(zJKFQz4rtZe*7g=`XYU zrrB%3R5D}e2u5&~vDX?+xlRNC+%{V0C5oha7ep+{p9bk@2QyY-O4}9n&;5>QuP7J8 z(bf--ofg9;j$pk}{DL~sfdFY;Z!tQvv-0o+Cahpm?-L#GAj;to_NrPRS3SJ( zG{|Zr>VMbBdbuIU%=T8|sRnBiU;)jwc^konifB9f(@#iwp?pY6Rn=L459b@o;V5k1 z{=NZ+))28Z7@Rxe%jrI~q!kDzFU~FgA*$va5b$46Hh}uZYugvn8k7CPesY~OKs{w? zY3Yf8O)Dh)(3VDKHQD3ZBXt%{4F2F^Qv|hw$csWhkE+BPWr8)X7xvvk7YVG-+@OkA z&QD_^@$Y&jzhZWQE!lxQ3;o>RbiDlWj`U5BS_3@o+KMC5g9tY2 z+kTioA(CC)@i`$$${TNtnY=hpWFsImxesJC&FKHOvkmBR?3SzjzkoIHPrM{x;C&W} zs)*MLvQd>2x`>B{Uuo(ET|z(JACw`l&z+fJjA#?DK3m z(7iegn+nq1r@$^CX}@f{BS4)0NkkH_0MTC3_MU9Ziw9ok&bm{XPC~ zPCbzg=?ULmmUS?MKQDaSq6`ee391X_w3$IBp!?h05i> zm4|-@0%30c#iLvaz{7mG+A1hFG>G^YwkB$`b0`M|pkp9DMz{gd2>wbtXIi}KO{AzC zd?*O%d)Il=_G+!s8>uai)@DFw`Jck&zfb3*zLhY&()GsY@)pXkp&2=KSkt>J7aVC* zE7Pba$}$C>{_5Xs$wo19Y6dK)f+&Mcl~6|^+^n%|o9^HA5e<8L-g7zmQz^jF1^>Sj z_ProLJbDitCE)i>mZt9fn_CN6*PqtiUndEEUI}>PJ4YW~dSt8s+g9w>HOUoB6adz4 z_*tf45Z&5e^hgvBRHj}s#oF%U5hv#juSxE4k7-``?gyX)^`i`YGXg;Vzgspt3ecFBhI*s8~0m zwrP>X`PRd#PZsR8Qh3&@lX3uBtuXN_FW7{?o%rFBs47k~5JOJ(H-((qk~ z9QXz(mgQZN4X^V*Ib>{+btfJ8GMxH_bNP$2HO28CU+( zrC#@4fvB%BEfPnEBKpumx6&!!K}S4hUhN;nc~O7l)d564hFq)9yFd75+-En^JUp|j z{=M7|4X1Z4F3d$x+ndggTK;fve;@L)%wXa7pc@&q?2uPV?gF!;?a@6lbGcIEmaXeU z&8q3If*5{&R_f^OqcDjT?i{IIiKvz|RgQeAy7D^k2VKkb!93^RA_0)EXD(y)DE)6q zB$NCfJA04iOF@~THBrLYGM)&hdm^Nf9rRDW2hCVI# z)54WO-8ZxXcJmbz=n!b1guwjdm|&D#Y9dOe+_WW`Hf2|#r?#2kkO_*Jw`tUq`?xch zWV(Exs`BD^2%tHXC*s}5(v>OotC}c45kN5#j8dq{9fE1nU+rJ`y_@4il}}Swwl+sL z*IO-Oyucu*$QhMa9!}df^c@eG8(`mgW~1i^kjMu1{7F(cUvB!c-zt z?J6=L>5ZULabZBurM4?pRo;C%Fn9O*UP!{o4?;m8mO$P1*a|(nm{iyK8O(k^ zHSwKSgB?QfmhDo1Cz?SUZWjOD>@Rbdvh`6Unr~z9 zEA1js_|op!ItnteqENPBaX-aRzcmi_!lR-Kk`sV?)0=9lah$~wxOSMzK&PVySX?E= zjj?&mId-p1iHfw#wAqrpHa6awu(Lrtw#&b`tvs}oW%#P4RyB)KdU-)braezuXEr`| z6kCw2iofz}R85iUMY}2W5nkzMF~1*sDw}B{oI9|*&&9?Q_%3&NJd79ZzV@w^#RcDz zpO+7Ltxc^A@+K)C-y|IpaN_g$gMz4f4Muh4GiNr8k=IiZ%{dFKq%W;g!DGh?*m zZMihVeP!rVmK$|j%58V5BwSs{qyM20xQ8aeBxhavna8vzX+lrp_>9egu2)CcnB&g`@$N<<%gE0o#otI^u9zYPtt}$pUc}gDm$ zkK84tr4cwdILg_uHxyTv6a~hr2-zA~9Hb=?CQ7C5 z3=$-xcpcJMY+SMmJI+=`Qxd>vlol1aBmHH^i4{YrO-VY|G9z|2?olJ(`P_<7m*dJ@ zYh$O}JxRq@Jd&TSVL~7bhN2%gFsFxb95XVtQ+X44W#b74^g0t3m6kFA4?*w?J8_t* zKq27-D}7)^hsL0eo;E&EAr?79S2kU(^EWnTSp|OYkF$o142+Dgo;sX`9Fj9RTnyn< zKq9xTz^TU7#D%139ZP_II?~B;(Y~HduvU=Aj77~P=C)i$x+78QEIblK+73i4>XwRt zEHq$-kc-mpMgA7$FpfY}>cl3t?PcnH!GuI^smW0hq&kpx;fo*_I^oOLh9ws|8 zx`~1#X2GhVfH@zHp$Y=|#Pbo&?!0!{uk^gLQQA?6KbiU0W1{EkIF30sz8b;Kzb@Ps zNcvVpytSsI7f_-?Vk;g|`GS&__}0XMig2KU@O@G^A96)U4$>5>ksIEX)+4p9@y9nI@XQkM z%qhvKKSI)2J4Y9b)Tk(>NRagG2FziNH}8&c^%2Jy_{zzgJc=rPyw*_DBHSEb9rVaG zgI#CI3XPZ=p_llB{M$Z*Ht_ik&t;+`|%HUP-b~qu*3t|vnbiIu!FY(AiW9|`gf-wyDESzzK z^co*i&u8>bc!A%G$181|NSaU!*}mb{-@&qQ0+xgI`mQ$f~1m*cIh813x~8BQ1p7ngX1b6Lj@;+U1Rs zl<{s91g1Ge5MuOf+VX?63CyxMDbzUPl^BA}USLi14Oub?UbVdOr*}0$n0GSi_|$ci zM0h4SIY@Vum_w<-$J>zxYt`E!?2Dg^zBGSeoNXO=7gfoNQ)t3!Zv7!Ko7lvOL8;ep zum)?qX4NmyP&!|mn|JnGH+T0ovV!^<59kV!+lp!3f*12LTVQjYQ!gBQf;0NE&4x~? z6LYW|(;E&a3G8O~+tK?~ud&cUkMeOs8Hf$FM^?)}S_SJ z>%`(qDCRDoE|!86W;|KS25s}xL2soE3=Q2YPPK2~oM$rE-m)xo(O?l}rdjpT-H8l2 zCYW5M)e5G+dRAcd`gjbv(+*f@yAk1)&tfr-y%xjllT77Xj#(kuLam>79YroiBk)Zu z^deA_h{ZtUfh$mrPhC`s-S}QmqX=;??`Jb>5BN~hojfZ$x;(g`nyjp>Vm4?wYIivV z{}#g3)D+F90&A5boM~RnDW9*+Fr;dgAXlxPl0VKA|6fc619tSYmdB zL<#P}znqv|aK|kSL`QwgqZg3t_=Vppr8TnvE=Vj}a7jJY(s+7OmaRn5FRkErTfSAc zd;;zP0bkvTX{s5U(nwX3xpthHN`j;|uGzo1P^iW)TxeZ(M;CHrhBt=i&&HxLZluA~ z#XI6l197KZzMNd=k*U=q(hCy&-syR+v95pCE)!YS@s6sf`-NZ#k5eh|?CczIP8t{j zEA(698}Ni!Rbj~kx$wiNAX=e_?x+syS}x<9_u|<~DY#McuYM!$AQQ8{`Om+LA15{v zk*wCv#K=GB(_vr8Oq^|5Ja+vuP$^uzG3aKx-C|NSNhqk|jL$D-QZ%z-kXO@A`8lrs zr^zZp`zH6}uAkL{C-m7_I~p6Jj}tCZ12WHRYYK;Yb&fUhn9P$pLz#e2jO_p zKD1s$J&T|$IOzc zLc*hG>p;5!w&Tq8ijj5S@kqtc^g;%lkPmuD8}LsZ-)urz2>Wc7CGOBuk1#!tHK>?lwBpbn|ESI~Z|n#SW{au%=Pc`0c__eCU7jy@v2gjA5q*7dO1xR|)my z)@@iVsP*GGTO^ky^n1fk7(zK4CZ>ZB_A^%RKpi;W>7j;XaesexXs__m!~3P^sj;DF zUox-X!FjPA(HEa%O1&_XWBE_*l1Wix4H&fzlCU1SH*da+q6~NEx)Z}5AxCvB5Z-bI zuc6#wCYKQfW@!1pMCU3ek;)Q5?MmGaH%kh%mYmYEl@A!jl0SmU*{tnCIO{cX$MUH} z24aD!oo@V+N-bD0Q-jA8eZUCc4+&nju$Ff@1V6kVCaD6UU%M?s@eAkohBFn1;L}|? zx~Uj?v|dC?4h|w>;VwG)Sl^z6>9^B2kdUxGo4}5zV;GarZ+U^`gksH#jmE%_5k5xN zCMi;?@(IQ_L@BIbUCgv@5+}A2v8!^tt_57;NKV5bhdwEf&WUca7uG);xJDirUq=qP z68_#9lvG3~`i@0(v^)25a4|6CxPnGW#6+Plv5o*ch!OT#8~u=g-NU}xjP+`(fS=xN zZ8`W+3B(q{h7d+!-(R(O<%O$fP-2#oL-bYLA2F!_?qmn0S9BKC;V#kgTnR!!96-&B zhU6lfaN#=gHLmiP*7&HgVy?6+2u7+jke-fWXl6~Cvq3%&jKR$Wp=->y z(&L9Z7}8`w==8`;(n6vdp69zt^r0|ej|+^_9Hr{@rSm<0x3b}aJ~S2Jw({F9*2flc zN>QSVft=x>xNnj)N`4(kJ8WQ7!qi)5H+zO;;TrTn>uN9xS5=4uEL5VDv_3l+`U!?R zWTPY$&A=OU6E#Q zehuu&j$L=LmhG-^JH0zXFMT7)BN~Bzx05?z7u(wwlq-Jz)s@=CC3hV8N{p56PiNv> zq!60s5+wv9@rdKyZFI3KH)b~;{ho33P6$4fhmGX^U1Ufl;-U4Imxt*DkFKL4dRQ?V z2L4Ce;+KjBC2*w8w~$EQ1!=7PywA>#9N)W5nPXQKCJnQhN&E{(Wyh|QjOstE@ob0V z$kYyITyaX7)WM(Zludjj*j#Aba^lPo#cSG^n{cG`Tws$wwo%;J=Vw&;^4oPG{-8Tu zN0wA&%$HYxcV1bgX2BpxFhjaqD`M(~LjUPg);2aVIBhz9jX=<}z75G}xd^8`Cjes2 zYI?P&KQgzEMJ-DENecL}@FoNg64kEDyWUF3qE8VBH);CsPVRn|-i*{4o3YQt|G{yS z_V^(#eQfLQa7`<+wr3PDv~6dYO*~H6lX-=Dik|YXjd|p1oYgM$>ieVF8b}KYI!uO9 z?q5_U7$Qv1wYts@*pu1(@Jdj8JSFfMe&hG)lo_ZTm~*unWto3 ztZ`PQxLv^`2QF<(!T^mlSaZ_WJ8ur&e1_85^kFD0lL~}wF>k=^Ql+8tl0VZ w!rrdsP5T%9+(64LLqxU4pe13ahgZnY3rI_>I<7J^t-+$Kx%E^c#!r{R^d-e=bTnwxLeER@zdl+co6PAd#^4T*Q zKXLFoCCBFtEr{_Gb}20PLq{H){r$SoC_XLk-oEMu%Z+5tyN-w0ejLp#TF|IOQO#Fr zv~N7Rtm7=1{5*TvG~p6wK6fzrI<0kFyQ+fKtWSt^c0yV@ib7f&{0{ZqqM?r88>7c1 z(N|L|R(@RhL)34cp@LqbTVYV?MStMb!<^KWmN4;eTgFZcCCy)Y*&`nr*h*9Xsl5PZ zk4*h+(q?vrB_Mz_{!11MYSw-k>h~W=W(pF#1klabpAf_!sKmk|r7+&l7?7)osA2ac zR>zJZHA8v?z+lwRR;W~2Pzn4SYT+?8i0}#$%KpnMW?Au+|B}Fn48N!$uIx`j78LIt z%z{hUy-(ZaT-a`|_En*P=Oyw-{0@eMe@SpsJD!v%nDw7~yjM+l%*Ac_xbgwV5A*tc zq(y^ci>CHz+1wVNwLX!6XE+7btGOREC$j3KL=DGJNs-iuVDNJ`5V9gcvOx_VyrM)c z)bZOla7FzHo4P^XfA-iHm^y@;Nk&&|D zR+EkB@1OU&Qu~FTK8yxC5kr)u*yPcuIY_+R-F3Rgj7$3uSdiuFna+A4Wd|wzGZAEDDa|B8IwV|$l zt(LYzzLM$X%E-|Be3ZWnDol=uOHck;E$XD=07+mTQ@go!54No3O2S<8)ObM`7$IbDw;W6;!cMx$soTm?Dn- z&6|ymGi+s3ynp~Si=WEI_S=cpJbp2K8yztcIk}!6W3w<;ptXDr{flw)@>uqPCI@f* zYVu`Mlz?^0F(*(z3e3w^8r|sM#)}LQuvv=*s{GH38nhn#I2NcJo7#pS3i&?IiD*PK z{`Qjlj$#_cVK3Iypg{)&yeRI95nHVB!KDG}^^K9BMEG@oUj6N*jV|V{Z#eGkh&e91 zlD@m%3S4>ltKUGqf4)`&`u7Z1St)*3b<$H5rvN@M(SLHt7p|~rfGXe4SMlb47Q{>zWbbl4O9Op2ym@Ta z)o`~ZG6MAUze(*Q8%hi47Fj2=L9GqY0yB|eM+E;Tho{u6_5Cdv^&&pf`1aE?nrDfO zNzVJ*hl-Y>4rzedU}$7Hkc-F*_IaMGSG7>4kCf{uoyi{7ic6Y`?lK81hhx5pdJw83; zu18e{sUCHGubGn!@3YAfj@gp0j73`cx!dTyF2xD1-Z^ufoU7v6zkm$sZb>qH)Iqka z^NA&QdK9SQZ?VQ6<%!HYN_U}*oVsUNYltd|u|ggkep?i~w^NMg3=ZbnHsU@S3G#8V z@`FzF8|FzoG(yFbtdLE2fT|>fJ}VD7BhtA1`qfRj{Vswa@$0BVmR!vb*fr41vx7Fx z3M5*!lWD4t?vJ{>E(OjGrWf5+3?T$CHcA}6Z`2KmMH#%(&vG4tm z;A3d@rT#Y9|M~k6x|^RlVJD z+=h;B6%QIN}1?{(9<_)vPmFAkx>@3it zlsqFD+QR~=v8dV;0;xhtjTp1W#9PXY@6yeDdrfG$v~D0BA0U-NxZvhott#>rZLa=S>F$=J8UtZ`YO18( zEKe0YJolkIt6mlgmr5Kafv1DKJMq4H6{QU8)@7g6EF*uwqc4aZ*{HrzpH`|&GS?Hd z=Et+qpVe@!Rjs%SR z8gY%|b<-6g!MsAPx|wcJQ5bib{zM7a z-C>-^LF@Nid^U@3JXt>3gI}$suA$%n?Ku%$)#Ty-n2E{P zPZltkZq^r?tHo2OKFJyN*JKEsVvoi6VHl{h+PAV0KVIAV=1Mi5JzhILRBGiHZDSrK z*fpdQDj&3Iu>W`$>BGOT)_1D;&79h%O0e(r)w;5a(7W$9Z*o zi%o3z)H+cnG&-kfH&ql~zc<-+aj9hByZ-3=-R&Vbe734qHG_6cpil(rjSB4duiq6H zB*u+#2a=d`&In#gA4oNP4a6QD4@*BISahXj%nHPtD8lRUR#x-Xg4#=6FnnBknpX^O zHugw$z85)rdfb}iuOA?>DMV8tA}Qd1f_$ zTvA)3d0|b^V}xG9Ttqa?|&}Fx%nnru<4vaS~>xG67s<(IubaU+DxzYRYfSI zTkW=L)S04<+G%2BYxE9HG*zlM-s(Ahvyk>!_btXGsV+ay#7skJm)};^-J`ke?XrOg zIu$Shu+ekgJr>FC?i2&5qFX+?cxvHxs0tF3S&Auzfw0)aoMEJpojHlU2!ynCWzjUr zBS``$R$55wtHOIYCu#CMMKvSh;_IvOs)xNJ=ESHdxqyC3j~j0X^KxWqM=bgglP`~t z^O^N=nx(c;mn$N?b!LY*oi?Z$wK>a}$O~7=(P)8yn?BZ}Z2NFyuy$D*&Q~f0Q%#ZP z>y}F9*t7gVYwTfxFb!m1tj<(lu=Yhk#25-#ZZE@)V2w#<;3^=|nO=W#+JKU0;*GAe z|Hjeu@=03rcGyig@0BbSzTgf_QISZ=_Cdb0{e+?5%_iKhIXQK4#b<{Qy7Yw^+^f@h z30pi84$$F?*)I9LjN#Vl@~_?BwK;6?ykJcEE5af3uL^I71BsHXQ2xd+S]a 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 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input-light.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input-light.png rename to apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input-dark.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input.selected.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input-dark.png rename to apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input.selected.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input-light.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input-light.png rename to apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input-dark.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input.selected.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input-dark.png rename to apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input.selected.png From eb2a1dc4d9eb7b8985d32153246c4e852731c09c Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Sat, 10 Jan 2026 22:17:18 +1100 Subject: [PATCH 025/155] feat: port jsx post view to new sprites --- .../src/services/juxt-web/views/ctr/post.tsx | 12 ++-- .../src/webfiles/ctr/css/juxt.css | 59 +++--------------- .../src/webfiles/ctr/css/sprites.css | 14 ++--- .../src/webfiles/ctr/images/sprites.png | Bin 14638 -> 14710 bytes .../sprites/{yeah-unselected.png => yeah.png} | Bin .../{yeah-selected.png => yeah.selected.png} | Bin .../src/webfiles/ctr/js/juxt.js | 3 + 7 files changed, 27 insertions(+), 61 deletions(-) rename apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/{yeah-unselected.png => yeah.png} (100%) rename apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/{yeah-selected.png => yeah.selected.png} (100%) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/post.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/post.tsx index fedb36c4..f378a96b 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/post.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/post.tsx @@ -38,7 +38,7 @@ export function CtrPostView(props: PostViewProps): ReactNode { ? ( - + {post.topic_tag} @@ -92,14 +92,18 @@ export function CtrPostView(props: PostViewProps): ReactNode { })} data-post={post.id} > - + - {post.empathy_count} + + {post.empathy_count} {' '} {!props.isReply ? ( - {post.reply_count} + <> + + {post.reply_count} + ) : null} diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css index 90bc2301..232913ee 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css @@ -481,17 +481,9 @@ header svg { background: -webkit-gradient(linear, left top, left bottom, from(#fff), color-stop(.5, #fff), color-stop(.8, #f6f6f6), color-stop(.96, #f5f5f5), to(#bbb)) 0 0 } -.sprite.yeah { - width: 20px; - height: 19px; - background-position: -55px 0; - background-size: 110px; +.inline-sprite { + display: inline-block; border: 0; - margin: 0 -} - -.yeah-button.selected .sprite.yeah { - background-position: -74px 0 } .feeling-selector .buttons { @@ -549,30 +541,6 @@ header svg { height: 208px } -.feeling-button-normal { - background-position-x: 0 !important -} - -.feeling-button-happy { - background-position-x: -28px !important -} - -.feeling-button-like { - background-position-x: -56px !important -} - -.feeling-button-surprised { - background-position-x: -84px !important -} - -.feeling-button-frustrated { - background-position-x: -113px !important -} - -.feeling-button-puzzled { - background-position-x: 31px !important -} - #body .image-selector .dropdown-toggle, button.load-more { text-align: center; @@ -614,30 +582,21 @@ button.load-more { z-index: 20 } -.sprite.feeling, -.sprite.reply { - padding-left: 20px; - background-size: 150px; - padding-bottom: 5px; +.yeah-count, +.reply-count { + padding-left: 5px; color: #646464; - margin-top: 6px; - width: auto -} - -.sprite.reply { - background-position-x: -127px; - background-position-y: 75px } -.sprite.feeling { - background-position-x: -128px; - background-position-y: 55px +.to-permalink-button span { + vertical-align: middle; } .to-permalink-button { float: right; margin-right: 10px; - margin-top: 2px + margin-top: 2px; + padding-top: 10px; } div.button-wrapper.center { diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css index abddc127..599ff6ce 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css @@ -142,19 +142,19 @@ To change `display` (e.g. `display: inline-block;`), we suggest using a common C width: 26px; height: 17px; } -.sprite-yeah-selected { - background-image: url(/images/sprites.png); - background-position: -112px 0px; - width: 20px; - height: 19px; -} .sprite-yeah-small { background-image: url(/images/sprites.png); background-position: -94px -84px; width: 16px; height: 16px; } -.sprite-yeah-unselected { +.sprite-yeah { + background-image: url(/images/sprites.png); + background-position: -112px 0px; + width: 20px; + height: 19px; +} +.sprite-yeah.selected { background-image: url(/images/sprites.png); background-position: -112px -19px; width: 20px; diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png index 22de31be42bd557ee1fce851f68254e2fd821462..de4aaac0e0739c5dce1c494ed318e0c9fd0df4fc 100644 GIT binary patch literal 14710 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 literal 14638 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 w!rrdsP5T%9+(64LLqxU4pe13ahgZnY3rI_>I< Date: Sat, 10 Jan 2026 22:51:16 +1100 Subject: [PATCH 026/155] feat: new sprites for older EJS routes too --- .../src/webfiles/ctr/css/juxt.css | 3 +- .../src/webfiles/ctr/partials/new_post.ejs | 30 +++++++++---------- .../webfiles/ctr/partials/post_template.ejs | 10 ++++--- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css index 232913ee..588a34ef 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css @@ -494,7 +494,8 @@ header svg { background-color: #846abd; margin-top: 5px; margin-right: 5px; - padding: 0 3px + padding: 0 3px; + font-size: 0; /* Collapse out whitespace */ } .feeling-selector .buttons li { diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs index 2d93cc4f..02da8671 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs +++ b/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs @@ -9,55 +9,55 @@

      -
    • + data-sound="SE_WAVE_MII_FACE_00"/>
    • - + data-sound="SE_WAVE_MII_FACE_01"/>
    • - + data-sound="SE_WAVE_MII_FACE_02"/>
    • - + data-sound="SE_WAVE_MII_FACE_03"/>
    • - + data-sound="SE_WAVE_MII_FACE_04"/>
    • - + data-sound="SE_WAVE_MII_FACE_05"/>
  • - +
  • - +
    diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/post_template.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/partials/post_template.ejs index 8d328974..c0497e9c 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/partials/post_template.ejs +++ b/apps/juxtaposition-ui/src/webfiles/ctr/partials/post_template.ejs @@ -10,7 +10,7 @@ <%if(post.topic_tag) {%> - + <%=post.topic_tag%> @@ -48,13 +48,15 @@
    href="/posts/<%= post.id %>"<%}%> class="to-permalink-button" data-pjax="#body"> <%if(!locals.yeah) {%> - <%=post.empathy_count %> + + <%=post.empathy_count %> <%if(!reply) {%> - <%=post.reply_count %> + + <%=post.reply_count %> <%}%> <%}%> From 3b1d1bec7c95516bf6654fe4a8703e7c1f8705fd Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Sat, 10 Jan 2026 23:04:34 +1100 Subject: [PATCH 027/155] feat: new sprites for community/user page --- .../src/webfiles/ctr/community.ejs | 8 ++--- .../src/webfiles/ctr/css/juxt.css | 34 +++--------------- .../src/webfiles/ctr/css/sprites.css | 2 +- .../{followers.png => follower-count.png} | Bin .../src/webfiles/ctr/user_page.ejs | 8 ++--- 5 files changed, 13 insertions(+), 39 deletions(-) rename apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/{followers.png => follower-count.png} (100%) diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs index 7f110bea..1c4608cf 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs +++ b/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs @@ -20,11 +20,11 @@ - - <%= bundle.numPosts %> + + <%= bundle.numPosts %> | - + <%= community.followers %> @@ -45,7 +45,7 @@ <%}%> <%if(community.permissions.open){%> <%}%> diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css index 588a34ef..6d940444 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css @@ -210,40 +210,14 @@ menu.tab-header { width: 400px } -.sprite { - display: inline-block; - width: 16px; - height: 16px; - background-size: 100px -} - -/* .feeling-selector .buttons input, */ -.sprite { - content: ""; - background-image: url(data:image/png;base64,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) -} - -.sprite.posts { - background-position: 0 0 -} - -.sprite.followers { - background-position: -17px 0; - width: 19px -} - -.sprite.tag { - background-position: -27px 0; - width: 11px; - background-size: 75px; - height: 14px -} - .text { font-size: 16px; position: absolute; right: 10px; - top: 2px + top: 2px; +} +.text > span > span { + vertical-align: middle; } menu.tab-header { diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css index 599ff6ce..ca900de5 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css @@ -88,7 +88,7 @@ To change `display` (e.g. `display: inline-block;`), we suggest using a common C width: 28px; height: 28px; } -.sprite-followers { +.sprite-follower-count { background-image: url(/images/sprites.png); background-position: -112px -72px; width: 19px; diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/followers.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/follower-count.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/followers.png rename to apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/follower-count.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/user_page.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/user_page.ejs index b425b344..1a7e5aee 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/user_page.ejs +++ b/apps/juxtaposition-ui/src/webfiles/ctr/user_page.ejs @@ -21,11 +21,11 @@ <%if(!pnid.deleted && !banned) {%> - - <%=numPosts%> + + <%=numPosts%> | - + <%= userContent.following_users.length - 1 %> @@ -38,7 +38,7 @@ <%}%> <%if(pnid.pid !== pid && !pnid.deleted && !banned) {%> <%}%> From d34514bb259f37e6fd255104f7ec2c1f9b914e2a Mon Sep 17 00:00:00 2001 From: gari martxel Date: Sat, 10 Jan 2026 14:13:04 +0100 Subject: [PATCH 028/155] locales(update): Updated Basque locale --- apps/juxtaposition-ui/src/translations/eu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/juxtaposition-ui/src/translations/eu.json b/apps/juxtaposition-ui/src/translations/eu.json index ef205a12..ea410f58 100644 --- a/apps/juxtaposition-ui/src/translations/eu.json +++ b/apps/juxtaposition-ui/src/translations/eu.json @@ -15,7 +15,7 @@ "close": "Itxi", "exit": "Irten", "next": "Hurrengoa", - "yeahs": "Yeah-k" + "yeahs": "Datseginak" }, "all_communities": { "text": "Komunitate denak", From f8367c84c9c733c59ef8231f45f2588d7d3e5c74 Mon Sep 17 00:00:00 2001 From: M Date: Sat, 10 Jan 2026 14:42:32 +0100 Subject: [PATCH 029/155] locales(update): Updated Basque locale --- apps/juxtaposition-ui/src/translations/eu.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/juxtaposition-ui/src/translations/eu.json b/apps/juxtaposition-ui/src/translations/eu.json index ea410f58..fd2db704 100644 --- a/apps/juxtaposition-ui/src/translations/eu.json +++ b/apps/juxtaposition-ui/src/translations/eu.json @@ -32,6 +32,17 @@ "followers": "Jarraitzaileak", "posts": "Argitalpenak", "tags": "Etiketak", - "recent": "Publikazio berriak" + "recent": "Publikazio berriak", + "popular": "Argitaralpen ospetsuak", + "verified": "Egiaztatutako argitaralpenak", + "new": "Argitalpen berriak" + }, + "user_page": { + "country": "Herrialdea", + "birthday": "Urtebetetzea", + "game_experience": "Joko mailak", + "friends": "Lagunak", + "following": "Jarraitzen", + "followers": "Jarraitzaileak" } } From 5828f6d4a7d86cd73e0cda5d3297dc75fbad9690 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Mon, 12 Jan 2026 19:19:38 +1100 Subject: [PATCH 030/155] fix: tidy up old assets --- .../src/webfiles/ctr/css/juxt.css | 6 ------ .../src/webfiles/ctr/images/icons.png | Bin 13005 -> 0 bytes 2 files changed, 6 deletions(-) delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/images/icons.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css index 6d940444..ef24e013 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css @@ -486,15 +486,9 @@ header svg { height: 28px; border: 0; text-indent: -3000px; - /* background-size: 172px; - background-position: 0 -29px; */ margin: -1px -2px -2px } -/* .feeling-selector .buttons input:checked { - background-position-y: -58px !important -} */ - .feeling-selector .icon { float: left; border: 1px solid gray; diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/icons.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/icons.png deleted file mode 100644 index e4794cc31cf496816a890f2dbb6261e9a8810761..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13005 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 Date: Mon, 12 Jan 2026 19:26:21 +1100 Subject: [PATCH 031/155] fix: pngcrush spritesheet not automated oops --- .../src/webfiles/ctr/images/sprites.png | Bin 14710 -> 7147 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png b/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png index de4aaac0e0739c5dce1c494ed318e0c9fd0df4fc..76680ce278cc8c6da9a0b3c3cc8715b4d7c52281 100644 GIT binary patch literal 7147 zcmYjWbyQT{yB>x{x;vz$Ly%7C4rv&gp<4uj85*RKkZzQ2L~3A=5{3?G5D=t>ZoGWo z?|1LIf1GpH+V9zWKX2@3J?A~KI$FwjIMg@*002)_MN#kR8S!-aV_`hqvFRmR005ds zRYf_2fW<=#Y;(ieIn2>frIxyr`TdLFF2Os?wV+uea98(jpuHAkgaw~*h>X$MNL!EUNj@=(`)n?cDVyKb_UI13zbSEoX#+wB zX@WQa(opshgqoDCUcQe?gIa#iXwsI2xW%{CVUhK_;0vS&(>i=Yfb%NOw@8=s%7(5G zONVdb)GaXTJ4*-H&j1s)1bOZd$cc82@pbR;^8shI!Yb*dh_7slJSLR_Q{mfQ(ud(o)ygtNW}3?G+>c!ws$| z1yY#1-{hSdP{6jV!$HzGVT|&Lma!rpAt7D6g@x0_2z=0_@ z({$sePp>XBkdO8?)gquxtWZP6Y?cfVubNdUw1RlT4 z7tTLt|F~dCLHz=>xfd{&Hihh14G#EC11%i+p$(hc@pQSWp@TyH0Rrczv~UDWyfa$( zTC}yd^&0o5kOC`^2vKb{bCbqwIao+j!t{6_&W28amp_9Gazm45R`DGdtG zx5C}(ph{oWwFpzg&v!hNSss4FHzKr)UJVaY32*-I&d(CWK|B7Ywr%_&Ea44G$zT5H z$J1r^E}Uq_zl3<#Nn0-v8Ao3wV}Kj*TE9CEi3>bUV0|ntYiRgnXi?b~Ab4rd}< zXXYYBGrmNX%T^G285#C_Jk*YMy~|HQse2G5>(e@t3Ld_;e;{x9wMh^0;)l!6;s^_F z&P}5X>35{S zWYCp09ach%j!|R%E>Nw7q|*Ws#aZM(4jEHdMmi2TjYA;5Mw{B)^M7&{*o7`n-XC3` zQ2rtv9czSdyPg@bx}IeM{U7dwxW>Lj_mEp>p;I^OF)FQsRGnqR{U``hT5CubKVzYd zGdN@Sz`oXopm?p_VQ6dtMio6nP)Po6Q}7q%eAcouI-SUqF#Jx=cHp3z(sR0yr3-so z*ePu8VzU-vIU}Em-{*O`MOMCf5|r?E{zbs9nh_Q%SH=%}e!5ZqgP8E?ja8!IyCTg! zweR>Y2vtzsmy^My^;oV>{`J2SKD{WfA2Zkv?e&%a_B|7~F&%y5zu}rbd|z}aemU!B z)6$!PgGcUKA{dOkkDdN_jC}j;7MG!CGUz%0ALbi&MkVklQ2;qvDV=*;PyBXJjknRn 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 From d182b89d55fadc74d30961aeab028140596f2ddf Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Mon, 12 Jan 2026 23:40:21 +1100 Subject: [PATCH 032/155] feat: use esbuild data-url support for spritesheet, rename to .sprite.sp-whatever shoud allow for neater css if all the sprites have the same class --- apps/juxtaposition-ui/package.json | 3 +- apps/juxtaposition-ui/scripts/sprites.ts | 33 +- .../juxt-web/views/ctr/newPostView.tsx | 18 +- .../src/services/juxt-web/views/ctr/post.tsx | 8 +- .../src/webfiles/ctr/community.ejs | 6 +- .../src/webfiles/ctr/css/juxt.css | 6 +- .../src/webfiles/ctr/css/sprites.css | 286 ++++++++---------- .../src/webfiles/ctr/js/juxt.js | 2 +- .../src/webfiles/ctr/partials/new_post.ejs | 18 +- .../webfiles/ctr/partials/post_template.ejs | 8 +- .../src/webfiles/ctr/user_page.ejs | 6 +- apps/juxtaposition-ui/tsup.config.ts | 5 +- package-lock.json | 182 +++++++---- 13 files changed, 300 insertions(+), 281 deletions(-) diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 4a64642c..d53ac74c 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -71,9 +71,10 @@ "esbuild-raw-plugin": "^0.3.1", "eslint": "^9.39.1", "globals": "^16.5.0", + "handlebars": "^4.7.8", "nodemon": "^3.1.11", "npm-run-all": "^4.1.5", - "spritesheet-templates": "^10.5.2", + "pixelsmith": "^2.6.0", "spritesmith": "^3.5.1", "tsup": "^8.5.1", "typescript": "^5.9.3" diff --git a/apps/juxtaposition-ui/scripts/sprites.ts b/apps/juxtaposition-ui/scripts/sprites.ts index 82e1615f..dc322b09 100644 --- a/apps/juxtaposition-ui/scripts/sprites.ts +++ b/apps/juxtaposition-ui/scripts/sprites.ts @@ -2,11 +2,11 @@ import { readdirSync, writeFileSync } from 'node:fs'; import { basename } from 'node:path'; import Spritesmith from 'spritesmith'; -// @ts-expect-error: missing upstream types -import templater from 'spritesheet-templates'; +import handlebars from 'handlebars'; const ctr = 'src/webfiles/ctr'; const ctr_images = ctr + '/images'; +const esbuild_ctr = '@/webfiles/ctr'; const sprite_paths = readdirSync(ctr_images + '/sprites') .filter(path => path.endsWith('.png')) .map(path => ctr_images + '/sprites/' + path); @@ -19,30 +19,29 @@ Spritesmith.run({ console.error(err); return; } - const { coordinates, properties, image } = result; + const { coordinates, image } = result; const sprites = Object.entries(coordinates).map(([name, coords]) => { return { - name: basename(name, '.png'), + name: basename(name, '.png').replaceAll('_', ':'), ...coords }; }); - const res = templater({ - sprites, - spritesheet: { - ...properties, - image: '/images/sprites.png' + // make the css + const res = handlebars.compile(` + /* This file is generated by scripts/sprites.ts */ + .sprite { + background-image: url(${esbuild_ctr}/images/sprites.png); } - }, { - format: 'css', - formatOpts: { - cssSelector: ({ name }: { name: string }) => { - /* Use underscore for :checked, :hover etc. */ - return '.sprite-' + name.replaceAll('_', ':'); - } + {{#each sprites}} + .sprite.sp-{{name}} { + background-position: -{{x}}px -{{y}}px; + width: {{width}}px; + height: {{height}}px; } - }); + {{/each}} + `)({ sprites }); writeFileSync(ctr + '/css/sprites.css', res, { encoding: 'utf-8' diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx index 79d7faae..9261cd03 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/newPostView.tsx @@ -7,38 +7,38 @@ const empathies = [ value: 0, miiFaceFile: 'normal_face.png', sound: 'SE_WAVE_MII_FACE_00', - className: 'sprite-feeling-normal', + className: 'sprite sp-feeling-normal', isDefault: true }, { value: 1, miiFaceFile: 'smile_open_mouth.png', sound: 'SE_WAVE_MII_FACE_01', - className: 'sprite-feeling-happy' + className: 'sprite sp-feeling-happy' }, { value: 2, miiFaceFile: 'wink_left.png', sound: 'SE_WAVE_MII_FACE_02', - className: 'sprite-feeling-like' + className: 'sprite sp-feeling-like' }, { value: 3, miiFaceFile: 'surprise_open_mouth.png', sound: 'SE_WAVE_MII_FACE_03', - className: 'sprite-feeling-surprise' + className: 'sprite sp-feeling-surprise' }, { value: 4, miiFaceFile: 'frustrated.png', sound: 'SE_WAVE_MII_FACE_04', - className: 'sprite-feeling-frustrated' + className: 'sprite sp-feeling-frustrated' }, { value: 5, miiFaceFile: 'sorrow.png', sound: 'SE_WAVE_MII_FACE_05', - className: 'sprite-feeling-puzzled' + className: 'sprite sp-feeling-puzzled' } ]; @@ -83,14 +83,14 @@ export function CtrNewPostView(props: NewPostViewProps): ReactNode { data-sound="SE_WAVE_BALLOON_OPEN" evt-click="window.alert('Screenshots are not ready yet. Check back soon!')" > - +

  • - +
  • - + - + {post.topic_tag} @@ -92,16 +92,16 @@ export function CtrPostView(props: PostViewProps): ReactNode { })} data-post={post.id} > - + - + {post.empathy_count} {' '} {!props.isReply ? ( <> - + {post.reply_count} ) diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs index 1c4608cf..eb9156d8 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs +++ b/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs @@ -20,11 +20,11 @@ - + <%= bundle.numPosts %> | - + <%= community.followers %> @@ -45,7 +45,7 @@ <%}%> <%if(community.permissions.open){%> <%}%> diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css index ef24e013..6110a263 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css @@ -532,7 +532,7 @@ button.load-more { position: relative; } -#body .image-selector .sprite-screenshot { +#body .image-selector .sprite.sp-screenshot { position: absolute; left: 50%; top: 50%; @@ -694,14 +694,14 @@ menu.tab-header.no-margin { display: inline-block } -.textarea-menu .sprite-text-input { +.textarea-menu .sprite.sp-text-input { position: absolute; pointer-events: none; top: 24px; left: 9px; } -.textarea-menu .sprite-memo-input { +.textarea-menu .sprite.sp-memo-input { position: absolute; pointer-events: none; top: 85px; diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css index ca900de5..2f848e6d 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css +++ b/apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css @@ -1,162 +1,126 @@ -/* -Icon classes can be used entirely standalone. They are named after their original file names. -Example usage in HTML: - -`display: block` sprite: -
    - -To change `display` (e.g. `display: inline-block;`), we suggest using a common CSS class: - -// CSS -.icon { - display: inline-block; -} - -// HTML - -*/ -.sprite-feeling-frustrated { - background-image: url(/images/sprites.png); - background-position: 0px 0px; - width: 28px; - height: 28px; -} -.sprite-feeling-frustrated:checked { - background-image: url(/images/sprites.png); - background-position: -28px 0px; - width: 28px; - height: 28px; -} -.sprite-feeling-happy { - background-image: url(/images/sprites.png); - background-position: 0px -28px; - width: 28px; - height: 28px; -} -.sprite-feeling-happy:checked { - background-image: url(/images/sprites.png); - background-position: -28px -28px; - width: 28px; - height: 28px; -} -.sprite-feeling-like { - background-image: url(/images/sprites.png); - background-position: -56px 0px; - width: 28px; - height: 28px; -} -.sprite-feeling-like:checked { - background-image: url(/images/sprites.png); - background-position: -56px -28px; - width: 28px; - height: 28px; -} -.sprite-feeling-normal { - background-image: url(/images/sprites.png); - background-position: 0px -56px; - width: 28px; - height: 28px; -} -.sprite-feeling-normal:checked { - background-image: url(/images/sprites.png); - background-position: -28px -56px; - width: 28px; - height: 28px; -} -.sprite-feeling-puzzled { - background-image: url(/images/sprites.png); - background-position: -56px -56px; - width: 28px; - height: 28px; -} -.sprite-feeling-puzzled:checked { - background-image: url(/images/sprites.png); - background-position: -84px 0px; - width: 28px; - height: 28px; -} -.sprite-feeling-surprise { - background-image: url(/images/sprites.png); - background-position: -84px -28px; - width: 28px; - height: 28px; -} -.sprite-feeling-surprise:checked { - background-image: url(/images/sprites.png); - background-position: -84px -56px; - width: 28px; - height: 28px; -} -.sprite-follower-count { - background-image: url(/images/sprites.png); - background-position: -112px -72px; - width: 19px; - height: 14px; -} -.sprite-memo-input { - background-image: url(/images/sprites.png); - background-position: -112px -38px; - width: 17px; - height: 17px; -} -.sprite-memo-input.selected { - background-image: url(/images/sprites.png); - background-position: -112px -55px; - width: 17px; - height: 17px; -} -.sprite-post-count { - background-image: url(/images/sprites.png); - background-position: -112px -86px; - width: 16px; - height: 16px; -} -.sprite-reply { - background-image: url(/images/sprites.png); - background-position: -78px -84px; - width: 16px; - height: 16px; -} -.sprite-screenshot { - background-image: url(/images/sprites.png); - background-position: 0px -84px; - width: 26px; - height: 22px; -} -.sprite-tag { - background-image: url(/images/sprites.png); - background-position: 0px -106px; - width: 12px; - height: 12px; -} -.sprite-text-input { - background-image: url(/images/sprites.png); - background-position: -26px -84px; - width: 26px; - height: 17px; -} -.sprite-text-input.selected { - background-image: url(/images/sprites.png); - background-position: -52px -84px; - width: 26px; - height: 17px; -} -.sprite-yeah-small { - background-image: url(/images/sprites.png); - background-position: -94px -84px; - width: 16px; - height: 16px; -} -.sprite-yeah { - background-image: url(/images/sprites.png); - background-position: -112px 0px; - width: 20px; - height: 19px; -} -.sprite-yeah.selected { - background-image: url(/images/sprites.png); - background-position: -112px -19px; - width: 20px; - height: 19px; -} + /* This file is generated by scripts/sprites.ts */ + .sprite { + background-image: url(@/webfiles/ctr/images/sprites.png); + } + .sprite.sp-feeling-frustrated { + background-position: -0px -0px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-frustrated:checked { + background-position: -28px -0px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-happy { + background-position: -0px -28px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-happy:checked { + background-position: -28px -28px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-like { + background-position: -56px -0px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-like:checked { + background-position: -56px -28px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-normal { + background-position: -0px -56px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-normal:checked { + background-position: -28px -56px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-puzzled { + background-position: -56px -56px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-puzzled:checked { + background-position: -84px -0px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-surprise { + background-position: -84px -28px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-surprise:checked { + background-position: -84px -56px; + width: 28px; + height: 28px; + } + .sprite.sp-follower-count { + background-position: -112px -72px; + width: 19px; + height: 14px; + } + .sprite.sp-memo-input { + background-position: -112px -38px; + width: 17px; + height: 17px; + } + .sprite.sp-memo-input.selected { + background-position: -112px -55px; + width: 17px; + height: 17px; + } + .sprite.sp-post-count { + background-position: -112px -86px; + width: 16px; + height: 16px; + } + .sprite.sp-reply { + background-position: -78px -84px; + width: 16px; + height: 16px; + } + .sprite.sp-screenshot { + background-position: -0px -84px; + width: 26px; + height: 22px; + } + .sprite.sp-tag { + background-position: -0px -106px; + width: 12px; + height: 12px; + } + .sprite.sp-text-input { + background-position: -26px -84px; + width: 26px; + height: 17px; + } + .sprite.sp-text-input.selected { + background-position: -52px -84px; + width: 26px; + height: 17px; + } + .sprite.sp-yeah-small { + background-position: -94px -84px; + width: 16px; + height: 16px; + } + .sprite.sp-yeah { + background-position: -112px -0px; + width: 20px; + height: 19px; + } + .sprite.sp-yeah.selected { + background-position: -112px -19px; + width: 20px; + height: 19px; + } + \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/js/juxt.js b/apps/juxtaposition-ui/src/webfiles/ctr/js/juxt.js index cd98c966..0cf110b5 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/js/juxt.js +++ b/apps/juxtaposition-ui/src/webfiles/ctr/js/juxt.js @@ -129,7 +129,7 @@ function initYeah() { } function yeah(e) { var el = e.currentTarget; - var sprite = el.querySelector('.sprite-yeah'); + var sprite = el.querySelector('.sprite.sp-yeah'); var id = el.getAttribute('data-post'); var parent = document.getElementById(id); var count = document.getElementById('count-' + id); diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs index 02da8671..260af361 100644 --- a/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs +++ b/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs @@ -9,32 +9,32 @@
    { props.page > 0 ? Previous Page : null } - Next Page + { props.hasNextPage ? Next Page : null }
    ) From 7d3e9e357c856c23c9daf52026a535b363598aed Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 13:54:33 +0100 Subject: [PATCH 073/155] fix: fixed searchbar styling --- .../juxt-web/views/web/admin/manageCommunityView.tsx | 2 +- .../src/services/juxt-web/views/web/admin/userListView.tsx | 2 +- apps/juxtaposition-ui/src/webfiles/web/css/web.css | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/manageCommunityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/manageCommunityView.tsx index 98d09fbc..cf32cddd 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/manageCommunityView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/manageCommunityView.tsx @@ -29,7 +29,7 @@ export function WebManageCommunityView(props: ManageCommunityViewProps): ReactNo
    - + diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/userListView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/userListView.tsx index e4937350..139da7cb 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/userListView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/userListView.tsx @@ -29,7 +29,7 @@ export function WebUserListView(props: UserListViewProps): ReactNode {
    - + { props.userCount } {' '} diff --git a/apps/juxtaposition-ui/src/webfiles/web/css/web.css b/apps/juxtaposition-ui/src/webfiles/web/css/web.css index 4a34d928..f0717c1c 100644 --- a/apps/juxtaposition-ui/src/webfiles/web/css/web.css +++ b/apps/juxtaposition-ui/src/webfiles/web/css/web.css @@ -1336,16 +1336,15 @@ li.reports .button-spacer { text-wrap: nowrap; } -#search { +.searchbar { margin-top: 16px; width: 20em; height: 2em; border-radius: 1em; - padding: 2ch; border: 1px solid var(--background-alt-alt); font-size: 1rem; background-color: var(--background-alt); - padding: 12px; + padding: 2px 12px; color: var(--text); } From d6fdeb03974f7167e8ddd827b846ef6943bdb62c Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 14:03:04 +0100 Subject: [PATCH 074/155] fix: fixed types for parseReq files --- .../juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts b/apps/juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts index 8591130d..a23cbeb0 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/routeUtils.ts @@ -43,7 +43,7 @@ export function getAuthedRequest(req: TReq): Aut return req as AuthRequest; } -export function parseReq(req: Request, ops?: ParseRequestOptions): ParsedRequest { +export function parseReq(req: Request, ops?: ParseRequestOptions): ParsedRequest { let body: any = undefined; let query: any = undefined; let params: any = undefined; From d987f6e2b41c94c549cee6568443bb293670589d Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 14:14:24 +0100 Subject: [PATCH 075/155] fix: fix undefined error in user check --- apps/juxtaposition-ui/src/middleware/consoleAuth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/juxtaposition-ui/src/middleware/consoleAuth.ts b/apps/juxtaposition-ui/src/middleware/consoleAuth.ts index 4a72a0e6..cadd2740 100644 --- a/apps/juxtaposition-ui/src/middleware/consoleAuth.ts +++ b/apps/juxtaposition-ui/src/middleware/consoleAuth.ts @@ -50,7 +50,7 @@ export const consoleAuth: RequestHandler = async (request, response, next) => { } } - const mayBypassAuthChecks = request.user.accessLevel === 3 || config.disableConsoleChecks; + const mayBypassAuthChecks = request.user?.accessLevel === 3 || config.disableConsoleChecks; // This section includes checks if a user is a developer and adds exceptions for these cases if (!request.pid) { From 4c44a8a79a102f9b76d5686626efc2469ecec7ea Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 14:14:42 +0100 Subject: [PATCH 076/155] fix: fixed left over ejs code in jsx --- .../src/services/juxt-web/views/web/admin/reportListView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/reportListView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/reportListView.tsx index 6e6a5312..54c33ebc 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/reportListView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/reportListView.tsx @@ -55,8 +55,8 @@ function Report(props: ReportProps): ReactNode {
    - - + +
  • From 094e5d6e968721b4eed6cec91106da30afe54541 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 14:23:31 +0100 Subject: [PATCH 077/155] fix: fix compile error --- .../src/services/juxt-web/routes/admin/admin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.tsx index 7c09db62..3f5f79bf 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/admin/admin.tsx @@ -165,7 +165,7 @@ adminRouter.get('/accounts/:pid', async function (req, res) { res.jsxForDirectory({ web: ( Date: Sun, 18 Jan 2026 14:23:51 +0100 Subject: [PATCH 078/155] fix: Fixed react fragments not working. Replaced with different type of conditional logic --- .../juxt-web/views/web/admin/moderateUserView.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/moderateUserView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/moderateUserView.tsx index ae1323ce..d6b040bd 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/moderateUserView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/admin/moderateUserView.tsx @@ -276,9 +276,6 @@ export function WebModerateUserView(props: ModerateUserViewProps): ReactNode { {props.reports.length === 0 ?

    There's nothing here...

    : null} {props.reports.map((report) => { const post = props.postsMap.find(post => post.id === report.post_id); - if (!post) { - return ; - } return (
  • @@ -320,7 +317,7 @@ export function WebModerateUserView(props: ModerateUserViewProps): ReactNode {
  • - + { post ? :

    Post could not be found

    }
  • ); @@ -341,9 +338,6 @@ export function WebModerateUserView(props: ModerateUserViewProps): ReactNode { {props.submittedReports.length === 0 ?

    There's nothing here...

    : null} {props.submittedReports.map((report) => { const post = props.postsMap.find(post => post.id === report.post_id); - if (!post) { - return ; - } return (
  • @@ -385,7 +379,7 @@ export function WebModerateUserView(props: ModerateUserViewProps): ReactNode {
  • - + { post ? :

    Post could not be found

    }
  • ); From 31a4d185f2d4ce822dcbb0c081477dd3b2e99dd3 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 15:36:20 +0100 Subject: [PATCH 079/155] feat: port posts.js to ts --- .../routes/console/{posts.js => posts.tsx} | 212 +++++++++++------- 1 file changed, 131 insertions(+), 81 deletions(-) rename apps/juxtaposition-ui/src/services/juxt-web/routes/console/{posts.js => posts.tsx} (64%) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx similarity index 64% rename from apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.js rename to apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx index 8cede055..faacf544 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx @@ -1,8 +1,9 @@ import crypto from 'crypto'; import express from 'express'; -import rateLimit from 'express-rate-limit'; +import { rateLimit } from 'express-rate-limit'; import moment from 'moment'; import multer from 'multer'; +import { z } from 'zod'; import { deletePostById, getPostById, getPostsByParentId } from '@/api/post'; import { config } from '@/config'; import { database } from '@/database'; @@ -11,8 +12,15 @@ import { logger } from '@/logger'; import { POST } from '@/models/post'; import { REPORT } from '@/models/report'; import { redisRemove } from '@/redisCache'; -import { createLogEntry, getCommunityHash, getInvalidPostRegex, getUserAccountData, newNotification } from '@/util'; +import { createLogEntry, getCommunityHash, getInvalidPostRegex, getUserAccountData } from '@/util'; import { addEmpathyById, removeEmpathyById } from '@/api/empathy'; +import { parseReq } from '@/services/juxt-web/routes/routeUtils'; +import type { Request, Response } from 'express'; +import type { InferSchemaType } from 'mongoose'; +import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { PostSchema } from '@/models/post'; +import type { CommunitySchema } from '@/models/communities'; +import type { HydratedSettingsDocument } from '@/models/settings'; const upload = multer({ dest: 'uploads/' }); export const postsRouter = express.Router(); @@ -41,13 +49,19 @@ const yeahLimit = rateLimit({ max: 10, // Limit each IP to 60 requests per `window` standardHeaders: true, legacyHeaders: true, - message: (req, _res) => { + message: (req: Request) => { return { status: 423, id: req.body.postID, count: 0 }; } }); postsRouter.get('/:post_id/oembed.json', async function (req, res) { - const post = await getPostById(req.tokens, req.params.post_id); + const { params, auth } = parseReq(req, { + params: z.object({ + post_id: z.string() + }) + }); + + const post = await getPostById(auth().tokens, params.post_id); if (!post) { return res.sendStatus(404); } @@ -89,31 +103,28 @@ postsRouter.get('/:post_id/oembed.json', async function (req, res) { }); postsRouter.post('/empathy', yeahLimit, async function (req, res) { - const post = await getPostById(req.tokens, req.body.postID); + const { body, auth } = parseReq(req, { + body: z.object({ + postID: z.string() + }) + }); + + const post = await getPostById(auth().tokens, body.postID); if (!post) { return res.sendStatus(404); } - const existingEmpathy = post.yeahs.indexOf(req.pid) !== -1; + const existingEmpathy = post.yeahs.indexOf(auth().pid) !== -1; const result = !existingEmpathy - ? await addEmpathyById(req.tokens, post.id) - : await removeEmpathyById(req.tokens, post.id); + ? await addEmpathyById(auth().tokens, post.id) + : await removeEmpathyById(auth().tokens, post.id); if (result === null) { res.send({ status: 423, id: post.id, count: post.empathy_count }); + return; } res.send({ status: 200, id: result.post_id, count: result.empathy_count }); - if (result.action === 'add' && req.pid !== post.pid) { - await newNotification({ - pid: post.pid, - type: 'yeah', - objectID: post.id, - userPID: req.pid, - link: `/posts/${post.id}` - }); - } - await redisRemove(`${post.pid}_user_page_posts`); }); @@ -122,23 +133,29 @@ postsRouter.post('/new', postLimit, upload.none(), async function (req, res) { }); postsRouter.get('/:post_id', async function (req, res) { - const userSettings = await database.getUserSettings(req.pid); - const userContent = await database.getUserContent(req.pid); + const { params, auth } = parseReq(req, { + params: z.object({ + post_id: z.string() + }) + }); - const post = await getPostById(req.tokens, req.params.post_id); + const userSettings = await database.getUserSettings(auth().pid); + const userContent = await database.getUserContent(auth().pid); + + const post = await getPostById(auth().tokens, params.post_id); if (!post) { return res.redirect('/404'); } if (post.parent) { - const parent = await getPostById(req.tokens, post.parent); + const parent = await getPostById(auth().tokens, post.parent); if (!parent) { return res.redirect('/404'); } return res.redirect(`/posts/${parent.id}`); } const community = await database.getCommunityByID(post.community_id); - const communityMap = await getCommunityHash(); - const replies = (await getPostsByParentId(req.tokens, post.id, 0))?.items ?? []; + const communityMap = getCommunityHash(); + const replies = (await getPostsByParentId(auth().tokens, post.id, 0))?.items ?? []; const postPNID = await getUserAccountData(post.pid); res.render(req.directory + '/post.ejs', { moment: moment, @@ -154,19 +171,28 @@ postsRouter.get('/:post_id', async function (req, res) { }); postsRouter.delete('/:post_id', async function (req, res) { - const post = await getPostById(req.tokens, req.params.post_id); + const { params, query, auth } = parseReq(req, { + params: z.object({ + post_id: z.string() + }), + query: z.object({ + reason: z.string().optional() + }) + }); + + const post = await getPostById(auth().tokens, params.post_id); if (!post) { return res.sendStatus(404); } - const result = await deletePostById(req.tokens, post.id, req.query.reason); + const result = await deletePostById(auth().tokens, post.id, query.reason); if (result === null) { return res.sendStatus(404); } // TODO move audit logging to backend - if (res.locals.moderator && req.pid !== post.pid) { - const reason = req.query.reason ? req.query.reason : 'Removed by moderator'; + if (res.locals.moderator && auth().pid !== post.pid) { + const reason = query.reason ? query.reason : 'Removed by moderator'; // TODO Temporarily disabled, moderators need a way to delete without notification // const postType = post.parent ? 'comment' : 'post'; @@ -179,9 +205,9 @@ postsRouter.delete('/:post_id', async function (req, res) { // }); await createLogEntry( - req.pid, + auth().pid, 'REMOVE_POST', - post.pid, + post.pid.toString(), `Post ${post.id} removed for: "${reason}"` ); } @@ -200,20 +226,28 @@ postsRouter.post('/:post_id/new', postLimit, upload.none(), async function (req, }); postsRouter.post('/:post_id/report', upload.none(), async function (req, res) { - const { reason, message, post_id } = req.body; - const post = await getPostById(req.tokens, post_id); + const { body, auth } = parseReq(req, { + body: z.object({ + reason: z.string(), + message: z.string(), + post_id: z.string() + }) + }); + + const { reason, message, post_id } = body; + const post = await getPostById(auth().tokens, post_id); if (!reason || !post_id || !post) { return res.redirect('/404'); } - const duplicate = await database.getDuplicateReports(req.pid, post_id); + const duplicate = await database.getDuplicateReports(auth().pid, post_id); if (duplicate) { return res.redirect(`/posts/${post.id}`); } const reportDoc = { pid: post.pid, - reported_by: req.pid, + reported_by: auth().pid, post_id, reason, message, @@ -226,7 +260,7 @@ postsRouter.post('/:post_id/report', upload.none(), async function (req, res) { return res.redirect(`/posts/${post.id}`); }); -function canPost(community, userSettings, parentPost, user) { +function canPost(community: InferSchemaType, userSettings: HydratedSettingsDocument, parentPost: InferSchemaType | null, user: GetUserDataResponse): boolean { const isReply = !!parentPost; const isPublicPostableCommunity = community.type >= 0 && community.type < 2; const isOpenCommunity = community.permissions.open; @@ -252,20 +286,39 @@ function canPost(community, userSettings, parentPost, user) { return isReply ? isOpenCommunity : isPublicPostableCommunity; } -async function newPost(req, res) { - const userSettings = await database.getUserSettings(req.pid); +async function newPost(req: Request, res: Response): Promise { + const { params, body, auth } = parseReq(req, { + params: z.object({ + post_id: z.string().optional() + }), + body: z.object({ + body: z.string(), + painting: z.string(), + screenshot: z.string(), + community_id: z.string(), + _post_type: z.string(), + bmp: z.stringbool(), + feeling_id: z.coerce.number(), + spoiler: z.boolean(), + is_app_jumpable: z.boolean(), + language_id: z.coerce.number() + }) + }); + + const userSettings = await database.getUserSettings(auth().pid); let parentPost = null; const postId = await generatePostUID(21); - let community = await database.getCommunityByID(req.body.community_id); - if (req.params.post_id) { - parentPost = await database.getPostByID(req.params.post_id.toString()); + let community = await database.getCommunityByID(body.community_id); + if (params.post_id) { + parentPost = await database.getPostByID(params.post_id.toString()); if (!parentPost) { - return res.sendStatus(403); + res.sendStatus(403); + return; } else { community = await database.getCommunityByID(parentPost.community_id); } } - if (req.params.post_id && (req.body.body === '' && req.body.painting === '' && req.body.screenshot === '')) { + if (params.post_id && (body.body === '' && body.painting === '' && body.screenshot === '')) { res.status(422); return res.redirect('/posts/' + req.params.post_id.toString()); } @@ -281,12 +334,12 @@ async function newPost(req, res) { } let paintingBlob = null; - if (req.body._post_type === 'painting' && req.body.painting) { + if (body._post_type === 'painting' && body.painting) { paintingBlob = await uploadPainting({ - blob: req.body.painting, + blob: body.painting, autodetectFormat: false, - isBmp: req.body.bmp === 'true', - pid: req.pid, + isBmp: body.bmp, + pid: auth().pid, postId }); if (paintingBlob === null) { @@ -298,10 +351,10 @@ async function newPost(req, res) { } } let screenshots = null; - if (req.body.screenshot) { + if (body.screenshot) { screenshots = await uploadScreenshot({ - blob: req.body.screenshot, - pid: req.pid, + blob: body.screenshot, + pid: auth().pid, postId }); if (screenshots === null) { @@ -314,7 +367,7 @@ async function newPost(req, res) { } let miiFace; - switch (parseInt(req.body.feeling_id)) { + switch (body.feeling_id) { case 1: miiFace = 'smile_open_mouth.png'; break; @@ -334,45 +387,47 @@ async function newPost(req, res) { miiFace = 'normal_face.png'; break; } - const body = req.body.body; - if (body && getInvalidPostRegex().test(body)) { + const postBody = req.body.body; + if (postBody && getInvalidPostRegex().test(postBody)) { // TODO - Log this error - return res.sendStatus(422); + res.sendStatus(422); + return; } - if (body && body.length > 280) { + if (postBody && postBody.length > 280) { // TODO - Log this error - return res.sendStatus(422); + res.sendStatus(422); + return; } const document = { title_id: community.title_id[0], community_id: community.olive_community_id, screen_name: userSettings.screen_name, - body: body, + body: postBody, painting: paintingBlob ?? '', screenshot: screenshots?.full ?? '', screenshot_length: screenshots?.fullLength ?? 0, screenshot_thumb: screenshots?.thumb ?? '', screenshot_aspect: screenshots?.aspect ?? '', - country_id: req.paramPackData ? req.paramPackData.country_id : 49, + country_id: auth().paramPackData?.country_id ?? 49, created_at: new Date(), - feeling_id: req.body.feeling_id, + feeling_id: body.feeling_id, id: postId, is_autopost: 0, - is_spoiler: (req.body.spoiler) ? 1 : 0, - is_app_jumpable: req.body.is_app_jumpable, - language_id: req.body.language_id, - mii: req.user.mii.data, - mii_face_url: `${config.cdnDomain}/mii/${req.user.pid}/${miiFace}`, - pid: req.pid, - platform_id: req.paramPackData ? req.paramPackData.platform_id : 0, - region_id: req.paramPackData ? req.paramPackData.region_id : 2, + is_spoiler: (body.spoiler) ? 1 : 0, + is_app_jumpable: body.is_app_jumpable, + language_id: body.language_id, + mii: auth().user.mii?.data, + mii_face_url: `${config.cdnDomain}/mii/${auth().pid}/${miiFace}`, + pid: auth().pid, + platform_id: auth().paramPackData?.platform_id ?? 0, + region_id: auth().paramPackData?.region_id ?? 2, verified: res.locals.moderator, parent: parentPost ? parentPost.id : null }; - const duplicatePost = await database.getDuplicatePosts(req.pid, document); - if (duplicatePost && req.params.post_id) { - return res.redirect('/posts/' + req.params.post_id.toString()); + const duplicatePost = await database.getDuplicatePosts(auth().pid, document); + if (duplicatePost && params.post_id) { + return res.redirect('/posts/' + params.post_id.toString()); } if ((document.body === '' && document.painting === '' && document.screenshot === '') || duplicatePost) { return res.redirect('/titles/' + community.olive_community_id + '/new'); @@ -383,26 +438,21 @@ async function newPost(req, res) { parentPost.reply_count = parentPost.reply_count + 1; parentPost.save(); } - if (parentPost && (parentPost.pid !== req.user.pid)) { - await newNotification({ - pid: parentPost.pid, - type: 'reply', - user: req.pid, - link: `/posts/${parentPost.id}` - }); - } if (parentPost) { - res.redirect('/posts/' + req.params.post_id.toString()); + if (!params.post_id) { + throw new Error('Has parent post but no postId, this is impossible'); + } + res.redirect('/posts/' + params.post_id.toString()); await redisRemove(`${parentPost.pid}_user_page_posts`); } else { res.redirect('/titles/' + community.olive_community_id + '/new'); - await redisRemove(`${req.pid}_user_page_posts`); + await redisRemove(`${auth().pid}_user_page_posts`); } } -async function generatePostUID(length) { +async function generatePostUID(length: number): Promise { let id = Buffer.from(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(length * 2))), 'binary').toString('base64').replace(/[+/]/g, '').substring(0, length); const inuse = await POST.findOne({ id }); - id = (inuse ? await generatePostUID() : id); + id = (inuse ? await generatePostUID(length) : id); return id; } From f173eb53722c85b2391356168a566e9a778c3c0a Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 16:02:52 +0100 Subject: [PATCH 080/155] feat: port error.ejs to jsx --- .../src/middleware/{jsx.ts => jsx.tsx} | 22 +++++- apps/juxtaposition-ui/src/server.ts | 10 +-- .../juxt-web/routes/console/communities.js | 10 ++- .../juxt-web/routes/console/messages.tsx | 4 +- .../juxt-web/routes/console/posts.tsx | 8 +- .../services/juxt-web/routes/web/login.tsx | 4 +- .../services/juxt-web/views/ctr/errorView.tsx | 28 +++++++ .../juxt-web/views/portal/errorView.tsx | 36 +++++++++ .../services/juxt-web/views/web/errorView.tsx | 52 +++++++++++++ apps/juxtaposition-ui/src/types/express.d.ts | 10 +++ .../src/webfiles/ctr/error.ejs | 15 ---- .../src/webfiles/portal/error.ejs | 30 -------- .../src/webfiles/web/error.ejs | 76 ------------------- 13 files changed, 168 insertions(+), 137 deletions(-) rename apps/juxtaposition-ui/src/middleware/{jsx.ts => jsx.tsx} (64%) create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/ctr/errorView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/portal/errorView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/error.ejs delete mode 100644 apps/juxtaposition-ui/src/webfiles/portal/error.ejs delete mode 100644 apps/juxtaposition-ui/src/webfiles/web/error.ejs diff --git a/apps/juxtaposition-ui/src/middleware/jsx.ts b/apps/juxtaposition-ui/src/middleware/jsx.tsx similarity index 64% rename from apps/juxtaposition-ui/src/middleware/jsx.ts rename to apps/juxtaposition-ui/src/middleware/jsx.tsx index baf39347..a8470b15 100644 --- a/apps/juxtaposition-ui/src/middleware/jsx.ts +++ b/apps/juxtaposition-ui/src/middleware/jsx.tsx @@ -1,6 +1,11 @@ import { renderToStaticMarkup } from 'react-dom/server'; -import type { RequestHandler } from 'express'; +import { buildContext } from '@/services/juxt-web/views/context'; +import { WebErrorView } from '@/services/juxt-web/views/web/errorView'; +import { CtrErrorView } from '@/services/juxt-web/views/ctr/errorView'; +import { PortalErrorView } from '@/services/juxt-web/views/portal/errorView'; import type { ReactElement } from 'react'; +import type { RequestHandler } from 'express'; +import type { ErrorViewProps } from '@/services/juxt-web/views/web/errorView'; const htmlDoctype = ''; @@ -40,5 +45,20 @@ export const jsxRenderer: RequestHandler = (request, response, next) => { throw new Error('Invalid directory to render JSX for'); }; + + response.renderError = (opt): typeof response => { + const props: ErrorViewProps = { + ctx: buildContext(response), + requestId: request.id, + code: opt.code, + message: opt.message + }; + response.jsxForDirectory({ + web: , + ctr: , + portal: + }); + return response; + }; next(); }; diff --git a/apps/juxtaposition-ui/src/server.ts b/apps/juxtaposition-ui/src/server.ts index 74839fcf..e8c84b82 100644 --- a/apps/juxtaposition-ui/src/server.ts +++ b/apps/juxtaposition-ui/src/server.ts @@ -70,10 +70,9 @@ logger.info('Creating 404 status handler'); app.use((req, res) => { req.log.warn('Page not found'); res.status(404); - res.render(req.directory + '/error.ejs', { + return res.renderError({ code: 404, - message: 'Page not found', - id: req.id + message: 'Page not found' }); }); @@ -93,10 +92,9 @@ app.use((error: Error | FetchError, req: Request, res: Response, next: NextFunct res.status(status); req.log.error(error, 'Request failed!'); - res.render(req.directory + '/error.ejs', { + return res.renderError({ code: status, - message: 'Error', - id: req.id + message: 'Error' }); }); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.js index 2cb0db49..cfaf5ec4 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.js @@ -64,7 +64,10 @@ communitiesRouter.get('/:communityID/related', async function (req, res) { } const community = await database.getCommunityByID(req.params.communityID.toString()); if (!community) { - return res.render(req.directory + '/error.ejs', { code: 404, message: 'Community not Found' }); + return res.renderError({ + code: 404, + message: 'Community not Found' + }); } const communityMap = await getCommunityHash(); const children = await database.getSubCommunities(community.olive_community_id); @@ -88,7 +91,10 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { } const community = await database.getCommunityByID(req.params.communityID.toString()); if (!community) { - return res.render(req.directory + '/error.ejs', { code: 404, message: 'Community not Found' }); + return res.renderError({ + code: 404, + message: 'Community not Found' + }); } if (!community.permissions) { diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/messages.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/messages.tsx index 8c447e1b..398f23bd 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/messages.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/messages.tsx @@ -88,7 +88,7 @@ messagesRouter.post('/new', async function (req, res) { }); if (paintingBlob === null) { res.status(422); - return res.render(req.directory + '/error.ejs', { + return res.renderError({ code: 422, message: 'Upload failed. Please try again later.' }); @@ -103,7 +103,7 @@ messagesRouter.post('/new', async function (req, res) { }); if (screenshots === null) { res.status(422); - return res.render(req.directory + '/error.ejs', { + return res.renderError({ code: 422, message: 'Upload failed. Please try again later.' }); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx index faacf544..0e010705 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx @@ -36,7 +36,7 @@ const postLimit = rateLimit({ } else if (req.body.community_id) { res.redirect('/titles/' + req.body.community_id); } else { - res.render(req.directory + '/error.ejs', { + return res.renderError({ code: 429, message: 'Too many new posts have been created.' }); @@ -344,10 +344,11 @@ async function newPost(req: Request, res: Response): Promise { }); if (paintingBlob === null) { res.status(422); - return res.render(req.directory + '/error.ejs', { + res.renderError({ code: 422, message: 'Upload failed. Please try again later.' }); + return; } } let screenshots = null; @@ -359,10 +360,11 @@ async function newPost(req: Request, res: Response): Promise { }); if (screenshots === null) { res.status(422); - return res.render(req.directory + '/error.ejs', { + res.renderError({ code: 422, message: 'Upload failed. Please try again later.' }); + return; } } diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx index 6b8117e0..2da2fcc7 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/web/login.tsx @@ -68,9 +68,9 @@ loginRouter.post('/', async (req, res) => { break; } if (discoveryStatus !== 0) { - return res.render(req.directory + '/error.ejs', { + return res.renderError({ code: 504, - message: message + message }); } const expiration = login.expiresIn * 60 * 60; diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/errorView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/errorView.tsx new file mode 100644 index 00000000..a9c998bf --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/errorView.tsx @@ -0,0 +1,28 @@ +import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root'; +import type { ReactNode } from 'react'; +import type { ErrorViewProps } from '@/services/juxt-web/views/web/errorView'; + +export function CtrErrorView(props: ErrorViewProps): ReactNode { + const title = `Error: ${props.code}`; + + return ( + + + +
    +

    Whoops! Looks like we couldn't find the page you're looking for.

    +

    Double-check your link or try again later

    +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/errorView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/errorView.tsx new file mode 100644 index 00000000..74d9a458 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/errorView.tsx @@ -0,0 +1,36 @@ +import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root'; +import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar'; +import type { ReactNode } from 'react'; +import type { ErrorViewProps } from '@/services/juxt-web/views/web/errorView'; + +export function PortalErrorView(props: ErrorViewProps): ReactNode { + const title = `Error: ${props.code}`; + + return ( + + + + +
    +
    +
    +

    + Error + {' '} + {props.code} + : + {' '} + {props.message} +

    +
    +

    Whoops! Looks like we couldn't find the page you're looking for.

    +

    Double-check your link or try again later

    + +
    +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx new file mode 100644 index 00000000..8f026abe --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx @@ -0,0 +1,52 @@ +import { WebLoginRoot } from '@/services/juxt-web/views/web/login'; +import { WebWrapper } from '@/services/juxt-web/views/web/root'; +import { PretendoLogo } from '@/services/juxt-web/views/web/icons'; +import type { ReactNode } from 'react'; +import type { ReqId } from 'pino-http'; +import type { RenderContext } from '@/services/juxt-web/views/context'; + +export type ErrorViewProps = { + ctx: RenderContext; + requestId: ReqId; + code: string | number; + message: string; +}; + +export function WebErrorView(props: ErrorViewProps): ReactNode { + const extraHead = Juxt - Error; + + return ( + + +
    + + + +

    + {props.code} + : + {' '} + {props.message} +

    +

    + View current + {' '} + server status + . +

    +

    + Or, + {' '} + go back to the homepage + . +

    +

    + Request ID: + {' '} + {props.requestId.toString()} +

    +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/types/express.d.ts b/apps/juxtaposition-ui/src/types/express.d.ts index ccfad3bf..d950cc17 100644 --- a/apps/juxtaposition-ui/src/types/express.d.ts +++ b/apps/juxtaposition-ui/src/types/express.d.ts @@ -25,6 +25,11 @@ declare global { disableDoctypeFor?: Request['directory'][]; }; + type ErrorRenderArgs = { + code: string | number; + message: string; + }; + interface Response { /** * Render JSX as static markup. Only static! No state or event handlers are supported. @@ -35,6 +40,11 @@ declare global { * Render JSX for directory set on req.directory as static markup. Only static! No state or event handlers are supported. */ jsxForDirectory: (el: JsxforDirectoryOptions) => Response; + + /** + * Render error page + */ + renderError: (el: ErrorRenderArgs) => Response; } } } diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/error.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/error.ejs deleted file mode 100644 index 90906b4b..00000000 --- a/apps/juxtaposition-ui/src/webfiles/ctr/error.ejs +++ /dev/null @@ -1,15 +0,0 @@ - - -<%- include('partials/head', { title: 'Error: ' + code }); %> - -
    - -
    -

    Whoops! Looks like we couldn't find the page you're looking for.

    -

    Double-check your link or try again later

    -
    -
    - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/webfiles/portal/error.ejs b/apps/juxtaposition-ui/src/webfiles/portal/error.ejs deleted file mode 100644 index abb62b4e..00000000 --- a/apps/juxtaposition-ui/src/webfiles/portal/error.ejs +++ /dev/null @@ -1,30 +0,0 @@ - - -<%- include('partials/head', { title: 'Error: ' + code }); %> - -<%- include('partials/nav_bar', { selection: -1, pid: pid }); %> -
    - -
    -
    -
    -

    Error <%= code %>: <%= message %>

    -
    -

    Whoops! Looks like we couldn't find the page you're looking for.

    -

    Double-check your link or try again later

    - -
    -
    -
    - - - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/webfiles/web/error.ejs b/apps/juxtaposition-ui/src/webfiles/web/error.ejs deleted file mode 100644 index d3d298dc..00000000 --- a/apps/juxtaposition-ui/src/webfiles/web/error.ejs +++ /dev/null @@ -1,76 +0,0 @@ - - - - - Juxt - Error - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - -
    - -
    -
    - \ No newline at end of file From 66b31eb870d801078226b289b8b21f4c159e638c Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 16:59:27 +0100 Subject: [PATCH 081/155] feat: port post.ejs to JSX --- .../juxt-web/routes/console/posts.tsx | 40 +++++--- .../juxt-web/views/ctr/postPageView.tsx | 99 +++++++++++++++++++ .../juxt-web/views/portal/postPageView.tsx | 93 +++++++++++++++++ .../juxt-web/views/web/newPostView.tsx | 2 +- .../src/services/juxt-web/views/web/post.tsx | 3 +- .../juxt-web/views/web/postPageView.tsx | 93 +++++++++++++++++ .../src/webfiles/ctr/post.ejs | 76 -------------- .../src/webfiles/portal/post.ejs | 82 --------------- .../src/webfiles/web/post.ejs | 74 -------------- 9 files changed, 316 insertions(+), 246 deletions(-) create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/ctr/postPageView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/portal/postPageView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/postPageView.tsx delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/post.ejs delete mode 100644 apps/juxtaposition-ui/src/webfiles/portal/post.ejs delete mode 100644 apps/juxtaposition-ui/src/webfiles/web/post.ejs diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx index 0e010705..07750cf6 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/posts.tsx @@ -1,7 +1,6 @@ import crypto from 'crypto'; import express from 'express'; import { rateLimit } from 'express-rate-limit'; -import moment from 'moment'; import multer from 'multer'; import { z } from 'zod'; import { deletePostById, getPostById, getPostsByParentId } from '@/api/post'; @@ -12,12 +11,17 @@ import { logger } from '@/logger'; import { POST } from '@/models/post'; import { REPORT } from '@/models/report'; import { redisRemove } from '@/redisCache'; -import { createLogEntry, getCommunityHash, getInvalidPostRegex, getUserAccountData } from '@/util'; +import { createLogEntry, getInvalidPostRegex, getUserAccountData } from '@/util'; import { addEmpathyById, removeEmpathyById } from '@/api/empathy'; import { parseReq } from '@/services/juxt-web/routes/routeUtils'; +import { buildContext } from '@/services/juxt-web/views/context'; +import { WebPostPageView } from '@/services/juxt-web/views/web/postPageView'; +import { CtrPostPageView } from '@/services/juxt-web/views/ctr/postPageView'; +import { PortalPostPageView } from '@/services/juxt-web/views/portal/postPageView'; import type { Request, Response } from 'express'; import type { InferSchemaType } from 'mongoose'; import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { PostPageViewProps } from '@/services/juxt-web/views/web/postPageView'; import type { PostSchema } from '@/models/post'; import type { CommunitySchema } from '@/models/communities'; import type { HydratedSettingsDocument } from '@/models/settings'; @@ -154,19 +158,31 @@ postsRouter.get('/:post_id', async function (req, res) { return res.redirect(`/posts/${parent.id}`); } const community = await database.getCommunityByID(post.community_id); - const communityMap = getCommunityHash(); + if (!community || !userSettings || !userContent) { + return res.redirect('/404'); + } + const replies = (await getPostsByParentId(auth().tokens, post.id, 0))?.items ?? []; const postPNID = await getUserAccountData(post.pid); - res.render(req.directory + '/post.ejs', { - moment: moment, - userSettings: userSettings, - userContent: userContent, - post: post, - replies: replies, - community: community, - communityMap: communityMap, + const canPost = ( + (community.permissions.open && community.type < 2) || + (community.admins && community.admins.indexOf(auth().pid) !== -1) || + (auth().user.accessLevel >= community.permissions.minimum_new_comment_access_level) + ) && userSettings.account_status === 0; + + const props: PostPageViewProps = { + ctx: buildContext(res), + community, + post, postPNID, - pnid: req.user + replies, + userContent, + canPost + }; + res.jsxForDirectory({ + web: , + ctr: , + portal: }); }); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/postPageView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/postPageView.tsx new file mode 100644 index 00000000..b2caf239 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/postPageView.tsx @@ -0,0 +1,99 @@ +import { CtrRoot, CtrPageBody } from '@/services/juxt-web/views/ctr/root'; +import { CtrPostView } from '@/services/juxt-web/views/ctr/post'; +import { CtrNewPostView } from '@/services/juxt-web/views/ctr/newPostView'; +import type { ReactNode } from 'react'; +import type { PostPageViewProps } from '@/services/juxt-web/views/web/postPageView'; + +export function CtrPostPageView(props: PostPageViewProps): ReactNode { + const { post } = props; + + return ( + + + + +
    +
    + +
    + {props.replies.map(replyPost => ( + + ))} +
    + {props.canPost ? : null} + +
    + + + +
    +

    + You are about to report a post with content which violates the Juxtaposition Code of Conduct. + This report will be sent to Pretendo's Juxtaposition administrators and not to the creator of the post. +

    +
    +

    Violation Type:

    + +
    + +
    + + + +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/postPageView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/postPageView.tsx new file mode 100644 index 00000000..a203ed21 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/postPageView.tsx @@ -0,0 +1,93 @@ +import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar'; +import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root'; +import { PortalNewPostView } from '@/services/juxt-web/views/portal/newPostView'; +import { PortalPostView } from '@/services/juxt-web/views/portal/post'; +import type { ReactNode } from 'react'; +import type { PostPageViewProps } from '@/services/juxt-web/views/web/postPageView'; + +export function PortalPostPageView(props: PostPageViewProps): ReactNode { + const { post } = props; + const pageTitle = !post.removed ? post.screen_name : 'Removed Post'; + + return ( + + + + +
    +
    + +
    + {props.replies.map(replyPost => ( + + ))} +
    + {props.canPost ? : null} + +
    +
    +

    Report Post

    +
    +
    + +
    +

    + You are about to report a post with content which violates the Juxtaposition Code of Conduct. + This report will be sent to Pretendo's Juxtaposition administrators and not to the creator of the post. +

    +
    +

    Violation Type:

    + +
    + +
    + + +
    +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/newPostView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/newPostView.tsx index 9f5ea735..c97fb1b9 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/newPostView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/newPostView.tsx @@ -48,7 +48,7 @@ export type NewPostViewProps = { name: string; url: string; show: string; - messagePid: number; + messagePid?: number; }; export function WebNewPostView(props: NewPostViewProps): ReactNode { diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/post.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/post.tsx index c8cd34c9..3c1d7341 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/post.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/post.tsx @@ -7,11 +7,12 @@ import type { ReactNode } from 'react'; import type { ContentSchema } from '@/models/content'; import type { PostSchema } from '@/models/post'; import type { RenderContext } from '@/services/juxt-web/views/context'; +import type { PostDto } from '@/api/post'; export type PostViewProps = { ctx: RenderContext; userContent?: InferSchemaType; - post: InferSchemaType; + post: InferSchemaType | PostDto; isReply?: boolean; isMainPost?: boolean; }; diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/postPageView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/postPageView.tsx new file mode 100644 index 00000000..b586d68a --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/postPageView.tsx @@ -0,0 +1,93 @@ +import { WebNavBar } from '@/services/juxt-web/views/web/navbar'; +import { WebRoot, WebWrapper } from '@/services/juxt-web/views/web/root'; +import { WebReportModalView } from '@/services/juxt-web/views/web/reportModalView'; +import { utils } from '@/services/juxt-web/views/utils'; +import { WebPostView } from '@/services/juxt-web/views/web/post'; +import type { ReactNode } from 'react'; +import type { InferSchemaType } from 'mongoose'; +import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { RenderContext } from '@/services/juxt-web/views/context'; +import type { ContentSchema } from '@/models/content'; +import type { CommunitySchema } from '@/models/communities'; +import type { PostDto } from '@/api/post'; + +export type PostPageViewProps = { + ctx: RenderContext; + post: PostDto; + userContent: InferSchemaType; + postPNID: GetUserDataResponse; + community: InferSchemaType; + replies: PostDto[]; + canPost: boolean; +}; + +function PostHead(props: PostPageViewProps): ReactNode { + const post = props.post; + const pageTitle = `Post by ${post.screen_name}`; + + if (post.removed) { + return ( + {pageTitle} + ); + } + + const title = `${post.screen_name} (@${props.postPNID.username}) - ${props.community.name}`; + const description = post.body + '\n\n' + + `${post.reply_count} 🗨️ ${post.empathy_count} ❤️`; + let image: string | null = null; + if (post.screenshot) { + image = utils.cdn(props.ctx, post.screenshot); + } else if (post.painting) { + image = utils.cdn(props.ctx, `/paintings/${post.pid}/${post.id}.png`); + } + + return ( + <> + {pageTitle} + + {/* Google / Search Engine Tags */} + + + {image ? : null} + + {/* Open Graph Meta Tags */} + + + + + + + + {/* Twitter Meta Tags */} + + + + + {image ? : null } + + + ); +} + +export function WebPostPageView(props: PostPageViewProps): ReactNode { + return ( + }> +

    Post

    + +
    +
    + + + + {props.replies.map(replyPost => ( +
    + + +
    + ))} +
    +
    + +
    + ); +} diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/post.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/post.ejs deleted file mode 100644 index c4417ce3..00000000 --- a/apps/juxtaposition-ui/src/webfiles/ctr/post.ejs +++ /dev/null @@ -1,76 +0,0 @@ - - -<%- include('partials/head', { title: lang.global.activity_feed }); %> - -
    - -
    -
    - <%- include('partials/post_template', { post: post, reply: false, pid: pid, yeah: true }); %> -
    - <% replies.forEach(function(post) { %> - <%- include('partials/post_template', { post: post, reply: true, pid: pid }); %> - <% }); %> -
    - <% if(( - (community.permissions.open && community.type < 2) || (community.admins && community.admins.indexOf(pid) !==-1) || - (pnid.accessLevel >= community.permissions.minimum_new_comment_access_level) - ) && userSettings.account_status === 0) {%> - <%- include('partials/new_post', { pid, lang, id: post.community_id, name: post.screen_name, url: `/posts/${post.id}/new`, show: 'post', message_pid: '' }); %> - <%}%> - -
    - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/webfiles/portal/post.ejs b/apps/juxtaposition-ui/src/webfiles/portal/post.ejs deleted file mode 100644 index a4abbb12..00000000 --- a/apps/juxtaposition-ui/src/webfiles/portal/post.ejs +++ /dev/null @@ -1,82 +0,0 @@ - - -<%- include('partials/head', { title: lang.global.activity_feed }); %> - -<%- include('partials/nav_bar', { selection: -1, pid: pid }); %> -
    - -
    -
    - <%- include('partials/post_template', { post: post, reply: false, pid: pid, mainPost: true }); %> -
    - <% replies.forEach(function(post) { %> - <%- include('partials/post_template', { post: post, reply: true, pid: pid }); %> - <% }); %> -
    - <% if(( - (community.permissions.open && community.type < 2) || (community.admins && community.admins.indexOf(pid) !==-1) || - (pnid.accessLevel >= community.permissions.minimum_new_comment_access_level) - ) && userSettings.account_status === 0) {%> - <%- include('partials/new_post', { pid, lang, id: post.community_id, name: post.screen_name, url: `/posts/${post.id}/new`, show: 'post', message_pid: '' }); %> - <%}%> - -
    - - diff --git a/apps/juxtaposition-ui/src/webfiles/web/post.ejs b/apps/juxtaposition-ui/src/webfiles/web/post.ejs deleted file mode 100644 index 254067cc..00000000 --- a/apps/juxtaposition-ui/src/webfiles/web/post.ejs +++ /dev/null @@ -1,74 +0,0 @@ - - - Post by <%= post.screen_name %> - <%- include('partials/head'); %> - <%if(!post.removed) {%> - - - - <% if(post.screenshot !== '') { %> - - <%} else if(post.painting !== '') { %> - - <%}%> - - - - - - - <% if(post.screenshot !== '') { %> - - <%} else if(post.painting !== '') { %> - - <%} else {%> - - <%}%> - - - - - (@<%= postPNID.username %>) - <%= community.name %>"/> - - - <% if(post.screenshot !== '') { %> - - <%} else if(post.painting !== '') { %> - - <%}%> - -<% } %> - - -
    - - <%- include('partials/nav_bar', { selection: 2, pid: pid }); %> -
    -
    -
    - <%if(!post.removed) {%> - <% if(pid !== 1000000000 && ( - (community.permissions.open && community.type < 2) || (community.admins && community.admins.indexOf(pid) !==-1) || - (pnid.accessLevel >= community.permissions.minimum_new_comment_access_level) - ) && userSettings.account_status === 0) {%> - - <%}%> - <%}%> - <%- include('partials/post_template', { post: post, reply: false, mainPost: true }); %> - - <% replies.forEach(function(post) { %> - <%- include('partials/post_template', { post: post, reply: true }); %> - - <% }); %> -
    -
    - <%- include('partials/report_modal', { pid }) %> -
    - - From 879f582b571126de0742d4548d7ed87c4939b365 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 18 Jan 2026 17:14:16 +0100 Subject: [PATCH 082/155] feat: port fatal_error.ejs to JSX --- .../src/middleware/checkBan.tsx | 13 ++++---- .../{consoleAuth.ts => consoleAuth.tsx} | 21 +++---------- .../src/middleware/discovery.tsx | 6 ++-- .../services/juxt-web/views/ctr/errorView.tsx | 31 ++++++++++++++++++- .../juxt-web/views/portal/errorView.tsx | 31 ++++++++++++++++++- .../services/juxt-web/views/web/errorView.tsx | 5 +++ .../src/webfiles/ctr/error_fatal.ejs | 17 ---------- .../src/webfiles/portal/error_fatal.ejs | 17 ---------- 8 files changed, 78 insertions(+), 63 deletions(-) rename apps/juxtaposition-ui/src/middleware/{consoleAuth.ts => consoleAuth.tsx} (82%) delete mode 100644 apps/juxtaposition-ui/src/webfiles/ctr/error_fatal.ejs delete mode 100644 apps/juxtaposition-ui/src/webfiles/portal/error_fatal.ejs diff --git a/apps/juxtaposition-ui/src/middleware/checkBan.tsx b/apps/juxtaposition-ui/src/middleware/checkBan.tsx index a74ef1c1..b4be17b7 100644 --- a/apps/juxtaposition-ui/src/middleware/checkBan.tsx +++ b/apps/juxtaposition-ui/src/middleware/checkBan.tsx @@ -4,6 +4,8 @@ import { config } from '@/config'; import { humanDate, humanFromNow } from '@/util'; import { WebLoginView } from '@/services/juxt-web/views/web/loginView'; import { buildContext } from '@/services/juxt-web/views/context'; +import { CtrFatalErrorView } from '@/services/juxt-web/views/ctr/errorView'; +import { PortalFatalErrorView } from '@/services/juxt-web/views/portal/errorView'; import type { RequestHandler } from 'express'; export const checkBan: RequestHandler = async (request, response, next) => { @@ -46,10 +48,7 @@ export const checkBan: RequestHandler = async (request, response, next) => { if (request.directory === 'web') { return response.jsx(); } else { - return response.render('portal/error_fatal.ejs', { - code: 5989999, - message: 'No access. Must be tester or dev' - }); + return response.jsx(); } } const userSettings = await db.getUserSettings(request.pid); @@ -84,9 +83,9 @@ export const checkBan: RequestHandler = async (request, response, next) => { if (request.directory === 'web') { return response.jsx(); } else { - return response.render(request.directory + '/error_fatal.ejs', { - message: banMessage, - code: banCode + return response.jsxForDirectory({ + portal: , + ctr: }); } } diff --git a/apps/juxtaposition-ui/src/middleware/consoleAuth.ts b/apps/juxtaposition-ui/src/middleware/consoleAuth.tsx similarity index 82% rename from apps/juxtaposition-ui/src/middleware/consoleAuth.ts rename to apps/juxtaposition-ui/src/middleware/consoleAuth.tsx index cadd2740..a7b3d2e6 100644 --- a/apps/juxtaposition-ui/src/middleware/consoleAuth.ts +++ b/apps/juxtaposition-ui/src/middleware/consoleAuth.tsx @@ -1,5 +1,6 @@ import { config } from '@/config'; import { logger } from '@/logger'; +import { PortalFatalErrorView } from '@/services/juxt-web/views/portal/errorView'; import { decodeParamPack, getPIDFromServiceToken, getUserAccountData, getUserDataFromToken, processLanguage } from '@/util'; import type { RequestHandler } from 'express'; @@ -54,31 +55,19 @@ export const consoleAuth: RequestHandler = async (request, response, next) => { // This section includes checks if a user is a developer and adds exceptions for these cases if (!request.pid) { - return response.render('portal/error_fatal.ejs', { - code: 5989999, - message: 'Unable to parse service token. Are you using a Nintendo Network ID?' - }); + return response.jsx(); } if (!request.user) { - return response.render('portal/error_fatal.ejs', { - code: 5989999, - message: 'Unable to fetch user data. Please try again later.' - }); + return response.jsx(); } if (!mayBypassAuthChecks && !request.paramPackData) { - return response.render('portal/error_fatal.ejs', { - code: 5989999, - message: 'Missing auth headers' - }); + return response.jsx(); } const userAgent = request.get('user-agent') ?? ''; const uaIsConsole = userAgent.includes('Nintendo WiiU') || userAgent.includes('Nintendo 3DS'); if (!mayBypassAuthChecks && !uaIsConsole) { - return response.render('portal/error_fatal.ejs', { - code: 5989999, - message: 'Invalid authentication method used.' - }); + return response.jsx(); } response.locals.uaIsConsole = uaIsConsole; diff --git a/apps/juxtaposition-ui/src/middleware/discovery.tsx b/apps/juxtaposition-ui/src/middleware/discovery.tsx index 1af22e49..43115c36 100644 --- a/apps/juxtaposition-ui/src/middleware/discovery.tsx +++ b/apps/juxtaposition-ui/src/middleware/discovery.tsx @@ -2,6 +2,7 @@ import { database as db } from '@/database'; import { config } from '@/config'; import { WebLoginView } from '@/services/juxt-web/views/web/loginView'; import { buildContext } from '@/services/juxt-web/views/context'; +import { PortalFatalErrorView } from '@/services/juxt-web/views/portal/errorView'; import type { RequestHandler } from 'express'; export const checkDiscovery: RequestHandler = async (request, response, next) => { @@ -24,10 +25,7 @@ export const checkDiscovery: RequestHandler = async (request, response, next) => if (request.directory === 'web') { return response.jsx(); } else { - return response.render('portal/error_fatal.ejs', { - message, - code: 5989999 - }); + return response.jsx(); } } else { request.guest_access = discovery ? discovery.guest_access : false; diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/errorView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/errorView.tsx index a9c998bf..cb072320 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/errorView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/errorView.tsx @@ -1,6 +1,7 @@ import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root'; +import { InlineScript } from '@/services/juxt-web/views/common'; import type { ReactNode } from 'react'; -import type { ErrorViewProps } from '@/services/juxt-web/views/web/errorView'; +import type { ErrorViewProps, FatalErrorViewProps } from '@/services/juxt-web/views/web/errorView'; export function CtrErrorView(props: ErrorViewProps): ReactNode { const title = `Error: ${props.code}`; @@ -26,3 +27,31 @@ export function CtrErrorView(props: ErrorViewProps): ReactNode { ); } + +const errorJs = ` +var e = document.getElementById('error'); +var code = parseInt(e.getAttribute('data-code')); +var message = e.getAttribute('data-message'); + +cave.error_callFreeErrorViewer(code, message); +cave.closeApplication(); +`; + +export function CtrFatalErrorView(props: FatalErrorViewProps): ReactNode { + return ( + + + + + +

    + You are not authorized to access this application ( + {props.code} + ) +

    +

    {props.message}

    + + + + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/errorView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/errorView.tsx index 74d9a458..3e08bb45 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/errorView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/errorView.tsx @@ -1,7 +1,8 @@ import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root'; import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar'; +import { InlineScript } from '@/services/juxt-web/views/common'; import type { ReactNode } from 'react'; -import type { ErrorViewProps } from '@/services/juxt-web/views/web/errorView'; +import type { ErrorViewProps, FatalErrorViewProps } from '@/services/juxt-web/views/web/errorView'; export function PortalErrorView(props: ErrorViewProps): ReactNode { const title = `Error: ${props.code}`; @@ -34,3 +35,31 @@ export function PortalErrorView(props: ErrorViewProps): ReactNode { ); } + +const errorJs = ` +var e = document.getElementById('error'); +var code = parseInt(e.getAttribute('data-code')); +var message = e.getAttribute('data-message'); + +wiiuErrorViewer.openByCodeAndMessage(code, message); +wiiuBrowser.closeApplication(); +`; + +export function PortalFatalErrorView(props: FatalErrorViewProps): ReactNode { + return ( + + + + + +

    + You are not authorized to access this application ( + {props.code} + ) +

    +

    {props.message}

    + + + + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx index 8f026abe..8f432279 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/errorView.tsx @@ -12,6 +12,11 @@ export type ErrorViewProps = { message: string; }; +export type FatalErrorViewProps = { + code: string | number; + message: string; +}; + export function WebErrorView(props: ErrorViewProps): ReactNode { const extraHead = Juxt - Error; diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/error_fatal.ejs b/apps/juxtaposition-ui/src/webfiles/ctr/error_fatal.ejs deleted file mode 100644 index 8d52864f..00000000 --- a/apps/juxtaposition-ui/src/webfiles/ctr/error_fatal.ejs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - -

    You are not authorized to access this application (<%=code%>)

    -

    <%=message%>

    - - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/webfiles/portal/error_fatal.ejs b/apps/juxtaposition-ui/src/webfiles/portal/error_fatal.ejs deleted file mode 100644 index 574f6db8..00000000 --- a/apps/juxtaposition-ui/src/webfiles/portal/error_fatal.ejs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - -

    You are not authorized to access this application (<%=code%>)

    -

    <%=message%>

    - - - \ No newline at end of file From 5b6ce3d402ad15325c14697be8a474a389fb667d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 02:43:44 +0000 Subject: [PATCH 083/155] chore(deps-dev): bump eslint from 9.39.1 to 9.39.2 Bumps [eslint](https://github.com/eslint/eslint) from 9.39.1 to 9.39.2. - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v9.39.1...v9.39.2) --- updated-dependencies: - dependency-name: eslint dependency-version: 9.39.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/juxtaposition-ui/package.json | 2 +- apps/miiverse-api/package.json | 2 +- package-lock.json | 66 +++++------------------------- 3 files changed, 12 insertions(+), 58 deletions(-) diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 87ff34b9..ded132be 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -71,7 +71,7 @@ "ejs-lint": "^2.0.1", "esbuild-fix-imports-plugin": "^1.0.23", "esbuild-raw-plugin": "^0.3.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "globals": "^16.5.0", "nodemon": "^3.1.11", "npm-run-all": "^4.1.5", diff --git a/apps/miiverse-api/package.json b/apps/miiverse-api/package.json index dd9463dc..2052d4c7 100644 --- a/apps/miiverse-api/package.json +++ b/apps/miiverse-api/package.json @@ -50,7 +50,7 @@ "@types/pako": "^2.0.4", "@types/supertest": "^6.0.3", "axios": "^1.13.2", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "newman": "^6.0.0", "ora": "^9.0.0", "postman-collection": "^5.2.0", diff --git a/package-lock.json b/package-lock.json index f15a9a08..953c3f42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,7 +70,7 @@ "ejs-lint": "^2.0.1", "esbuild-fix-imports-plugin": "^1.0.23", "esbuild-raw-plugin": "^0.3.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "globals": "^16.5.0", "nodemon": "^3.1.11", "npm-run-all": "^4.1.5", @@ -118,7 +118,7 @@ "@types/pako": "^2.0.4", "@types/supertest": "^6.0.3", "axios": "^1.13.2", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "newman": "^6.0.0", "ora": "^9.0.0", "postman-collection": "^5.2.0", @@ -1783,9 +1783,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { @@ -4753,7 +4753,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -4961,58 +4961,12 @@ "node": ">=16.20.1" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/buffer/node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "peer": true - }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -6260,9 +6214,9 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "dependencies": { @@ -6272,7 +6226,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", From be86c22932b901ac8c85f652761d7086f3356ac6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 02:43:48 +0000 Subject: [PATCH 084/155] chore(deps): bump react-dom from 19.2.0 to 19.2.3 Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) from 19.2.0 to 19.2.3. - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.3/packages/react-dom) --- updated-dependencies: - dependency-name: react-dom dependency-version: 19.2.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/juxtaposition-ui/package.json | 2 +- package-lock.json | 64 +++++------------------------- 2 files changed, 10 insertions(+), 56 deletions(-) diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 87ff34b9..6010f7b6 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -50,7 +50,7 @@ "pjax": "^0.2.8", "prom-client": "^15.1.3", "react": "^19.1.1", - "react-dom": "^19.2.0", + "react-dom": "^19.2.3", "redis": "^5.10.0", "tsx": "^4.20.6", "zod": "^4.1.13" diff --git a/package-lock.json b/package-lock.json index f15a9a08..a0767521 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "pjax": "^0.2.8", "prom-client": "^15.1.3", "react": "^19.1.1", - "react-dom": "^19.2.0", + "react-dom": "^19.2.3", "redis": "^5.10.0", "tsx": "^4.20.6", "zod": "^4.1.13" @@ -4753,7 +4753,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -4961,58 +4961,12 @@ "node": ">=16.20.1" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/buffer/node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "peer": true - }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -11020,24 +10974,24 @@ } }, "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.0" + "react": "^19.2.3" } }, "node_modules/react-is": { From f04d9216dc234d401ee5e873a569a9a29912e76c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 06:52:53 +0000 Subject: [PATCH 085/155] chore(deps): bump tsx from 4.20.6 to 4.21.0 Bumps [tsx](https://github.com/privatenumber/tsx) from 4.20.6 to 4.21.0. - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.20.6...v4.21.0) --- updated-dependencies: - dependency-name: tsx dependency-version: 4.21.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/juxtaposition-ui/package.json | 2 +- package-lock.json | 467 ++++++++++++++++++++++++++++- 2 files changed, 463 insertions(+), 6 deletions(-) diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index a9de5630..fd729f3e 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -52,7 +52,7 @@ "react": "^19.1.1", "react-dom": "^19.2.3", "redis": "^5.10.0", - "tsx": "^4.20.6", + "tsx": "^4.21.0", "zod": "^4.1.13" }, "devDependencies": { diff --git a/package-lock.json b/package-lock.json index 9ec6e82c..62975e1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "react": "^19.1.1", "react-dom": "^19.2.3", "redis": "^5.10.0", - "tsx": "^4.20.6", + "tsx": "^4.21.0", "zod": "^4.1.13" }, "devDependencies": { @@ -13248,12 +13248,12 @@ } }, "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "license": "MIT", "dependencies": { - "esbuild": "~0.25.0", + "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -13266,6 +13266,463 @@ "fsevents": "~2.3.3" } }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", From b15d894de983659243b36b961c3088dc1476ed62 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Tue, 20 Jan 2026 17:58:16 +1100 Subject: [PATCH 086/155] chore(ui): src/webfiles -> webfiles --- .../webfiles/ctr/all_communities.ejs | 0 .../{src => }/webfiles/ctr/communities.ejs | 0 .../{src => }/webfiles/ctr/community.ejs | 0 .../{src => }/webfiles/ctr/css/firstrun.css | 0 .../{src => }/webfiles/ctr/css/juxt.css | 0 .../webfiles/ctr/css/sprites.css | 126 ++++++++++++++++++ .../{src => }/webfiles/ctr/error.ejs | 0 .../{src => }/webfiles/ctr/error_fatal.ejs | 0 .../{src => }/webfiles/ctr/feed.ejs | 0 .../{src => }/webfiles/ctr/guest_notice.ejs | 0 .../webfiles/ctr/images/background.png | Bin .../webfiles/ctr/images/bandwidthalert.png | Bin .../{src => }/webfiles/ctr/images/banner.png | Bin .../{src => }/webfiles/ctr/images/header.png | Bin .../webfiles/ctr/images/headline-green.png | Bin .../webfiles/ctr/images/headline.png | Bin .../webfiles/ctr/images/sprites.png | Bin 0 -> 14710 bytes .../ctr/images/sprites/feeling-frustrated.png | Bin .../sprites/feeling-frustrated_checked.png | Bin .../ctr/images/sprites/feeling-happy.png | Bin .../images/sprites/feeling-happy_checked.png | Bin .../ctr/images/sprites/feeling-like.png | Bin .../images/sprites/feeling-like_checked.png | Bin .../ctr/images/sprites/feeling-normal.png | Bin .../images/sprites/feeling-normal_checked.png | Bin .../ctr/images/sprites/feeling-puzzled.png | Bin .../sprites/feeling-puzzled_checked.png | Bin .../ctr/images/sprites/feeling-surprise.png | Bin .../sprites/feeling-surprise_checked.png | Bin .../ctr/images/sprites/follower-count.png | Bin .../ctr/images/sprites/memo-input.png | Bin .../images/sprites/memo-input.selected.png | Bin .../ctr/images/sprites/post-count.png | Bin .../webfiles/ctr/images/sprites/reply.png | Bin .../ctr/images/sprites/screenshot.png | Bin .../webfiles/ctr/images/sprites/tag.png | Bin .../ctr/images/sprites/text-input.png | Bin .../images/sprites/text-input.selected.png | Bin .../ctr/images/sprites/yeah-small.png | Bin .../webfiles/ctr/images/sprites/yeah.png | Bin .../ctr/images/sprites/yeah.selected.png | Bin .../{src => }/webfiles/ctr/images/tab.png | Bin .../{src => }/webfiles/ctr/js/debug.js | 0 .../{src => }/webfiles/ctr/js/firstrun.js | 0 .../{src => }/webfiles/ctr/js/juxt.js | 0 .../{src => }/webfiles/ctr/js/pjax.js | 0 .../{src => }/webfiles/ctr/notifications.ejs | 0 .../webfiles/ctr/partials/following_list.ejs | 0 .../{src => }/webfiles/ctr/partials/head.ejs | 0 .../webfiles/ctr/partials/new_post.ejs | 0 .../webfiles/ctr/partials/notifications.ejs | 0 .../webfiles/ctr/partials/post_template.ejs | 0 .../webfiles/ctr/partials/posts_list.ejs | 0 .../webfiles/ctr/partials/requests.ejs | 0 .../{src => }/webfiles/ctr/post.ejs | 0 .../{src => }/webfiles/ctr/settings.ejs | 0 .../webfiles/ctr/sub_communities.ejs | 0 .../{src => }/webfiles/ctr/user_menu.ejs | 0 .../{src => }/webfiles/ctr/user_page.ejs | 0 .../webfiles/portal/all_communities.ejs | 0 .../{src => }/webfiles/portal/communities.ejs | 0 .../{src => }/webfiles/portal/community.ejs | 0 .../webfiles/portal/css/firstrun.css | 0 .../{src => }/webfiles/portal/css/juxt.css | 0 .../{src => }/webfiles/portal/error.ejs | 0 .../{src => }/webfiles/portal/error_fatal.ejs | 0 .../{src => }/webfiles/portal/feed.ejs | 0 .../webfiles/portal/fonts/Poppins-Light.eot | Bin .../webfiles/portal/fonts/Poppins-Light.otf | Bin .../webfiles/portal/fonts/Poppins-Light.svg | 0 .../webfiles/portal/fonts/Poppins-Light.ttf | Bin .../webfiles/portal/fonts/Poppins-Light.woff | Bin .../webfiles/portal/guest_notice.ejs | 0 .../portal/images/add-post-no-image.png | Bin .../webfiles/portal/images/background.png | Bin .../webfiles/portal/images/bandwidthalert.png | Bin .../webfiles/portal/images/bandwidthlost.png | Bin .../webfiles/portal/images/banner.png | Bin .../webfiles/portal/images/favicon.ico | Bin .../portal/images/splash-background.png | Bin .../{src => }/webfiles/portal/js/debug.js | 0 .../{src => }/webfiles/portal/js/firstrun.js | 0 .../{src => }/webfiles/portal/js/juxt.js | 0 .../webfiles/portal/notifications.ejs | 0 .../portal/partials/following_list.ejs | 0 .../webfiles/portal/partials/head.ejs | 0 .../webfiles/portal/partials/nav_bar.ejs | 0 .../webfiles/portal/partials/new_post.ejs | 0 .../webfiles/portal/partials/not_ready.ejs | 0 .../portal/partials/notifications.ejs | 0 .../portal/partials/post_template.ejs | 0 .../webfiles/portal/partials/posts_list.ejs | 0 .../webfiles/portal/partials/requests.ejs | 0 .../webfiles/portal/partials/updates.ejs | 0 .../{src => }/webfiles/portal/post.ejs | 0 .../webfiles/portal/scroll_wrapper.ejs | 0 .../{src => }/webfiles/portal/settings.ejs | 0 .../webfiles/portal/sub_communities.ejs | 0 .../{src => }/webfiles/portal/user_page.ejs | 0 .../webfiles/web/all_communities.ejs | 0 .../{src => }/webfiles/web/communities.ejs | 0 .../{src => }/webfiles/web/community.ejs | 0 .../{src => }/webfiles/web/css/first_run.css | 0 .../{src => }/webfiles/web/css/login.css | 0 .../{src => }/webfiles/web/css/web.css | 0 .../{src => }/webfiles/web/edit_community.ejs | 0 .../{src => }/webfiles/web/error.ejs | 0 .../{src => }/webfiles/web/feed.ejs | 0 .../webfiles/web/images/add-post-no-image.png | Bin .../webfiles/web/images/bandwidthalert.png | Bin .../webfiles/web/images/bandwidthlost.png | Bin .../{src => }/webfiles/web/images/banner.png | Bin .../{src => }/webfiles/web/images/favicon.ico | Bin .../web/images/icons/icon-128x128.png | Bin .../web/images/icons/icon-144x144.png | Bin .../web/images/icons/icon-152x152.png | Bin .../web/images/icons/icon-192x192.png | Bin .../web/images/icons/icon-384x384.png | Bin .../web/images/icons/icon-512x512.png | Bin .../webfiles/web/images/icons/icon-72x72.png | Bin .../webfiles/web/images/icons/icon-96x96.png | Bin .../webfiles/web/images/splash-background.png | Bin .../{src => }/webfiles/web/js/admin.js | 0 .../{src => }/webfiles/web/js/login.js | 0 .../{src => }/webfiles/web/js/menus.js | 0 .../{src => }/webfiles/web/js/painting.js | 0 .../{src => }/webfiles/web/js/post.js | 0 .../{src => }/webfiles/web/js/reports.js | 0 .../{src => }/webfiles/web/js/web.js | 0 .../webfiles/web/manage_communities.ejs | 0 .../{src => }/webfiles/web/manifest.json | 0 .../{src => }/webfiles/web/me_page.ejs | 0 .../{src => }/webfiles/web/moderate_user.ejs | 0 .../{src => }/webfiles/web/new_community.ejs | 0 .../{src => }/webfiles/web/notifications.ejs | 0 .../webfiles/web/partials/assets/bin_icon.svg | 0 .../web/partials/assets/flag_icon.svg | 0 .../web/partials/assets/heart_icon.svg | 0 .../web/partials/assets/menu_icon.svg | 0 .../web/partials/assets/reply_icon.svg | 0 .../web/partials/assets/share_icon.svg | 0 .../webfiles/web/partials/following_list.ejs | 0 .../{src => }/webfiles/web/partials/head.ejs | 0 .../webfiles/web/partials/more_posts.ejs | 0 .../webfiles/web/partials/nav_bar.ejs | 0 .../webfiles/web/partials/new_post.ejs | 0 .../webfiles/web/partials/notifications.ejs | 0 .../webfiles/web/partials/post_template.ejs | 0 .../webfiles/web/partials/posts_list.ejs | 0 .../webfiles/web/partials/report_modal.ejs | 0 .../{src => }/webfiles/web/post.ejs | 0 .../{src => }/webfiles/web/reports.ejs | 0 .../{src => }/webfiles/web/robots.txt | 0 .../{src => }/webfiles/web/user_page.ejs | 0 .../{src => }/webfiles/web/users.ejs | 0 155 files changed, 126 insertions(+) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/all_communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/community.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/css/firstrun.css (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/css/juxt.css (100%) create mode 100644 apps/juxtaposition-ui/webfiles/ctr/css/sprites.css rename apps/juxtaposition-ui/{src => }/webfiles/ctr/error.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/error_fatal.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/feed.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/guest_notice.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/background.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/bandwidthalert.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/banner.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/header.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/headline-green.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/headline.png (100%) create mode 100644 apps/juxtaposition-ui/webfiles/ctr/images/sprites.png rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-frustrated.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-frustrated_checked.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-happy.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-happy_checked.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-like.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-like_checked.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-normal.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-normal_checked.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-puzzled.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-puzzled_checked.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-surprise.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/feeling-surprise_checked.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/follower-count.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/memo-input.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/memo-input.selected.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/post-count.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/reply.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/screenshot.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/tag.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/text-input.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/text-input.selected.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/yeah-small.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/yeah.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/sprites/yeah.selected.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/images/tab.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/js/debug.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/js/firstrun.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/js/juxt.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/js/pjax.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/notifications.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/partials/following_list.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/partials/head.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/partials/new_post.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/partials/notifications.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/partials/post_template.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/partials/posts_list.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/partials/requests.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/post.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/settings.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/sub_communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/user_menu.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/ctr/user_page.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/all_communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/community.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/css/firstrun.css (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/css/juxt.css (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/error.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/error_fatal.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/feed.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/fonts/Poppins-Light.eot (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/fonts/Poppins-Light.otf (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/fonts/Poppins-Light.svg (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/fonts/Poppins-Light.ttf (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/fonts/Poppins-Light.woff (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/guest_notice.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/images/add-post-no-image.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/images/background.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/images/bandwidthalert.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/images/bandwidthlost.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/images/banner.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/images/favicon.ico (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/images/splash-background.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/js/debug.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/js/firstrun.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/js/juxt.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/notifications.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/following_list.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/head.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/nav_bar.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/new_post.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/not_ready.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/notifications.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/post_template.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/posts_list.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/requests.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/partials/updates.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/post.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/scroll_wrapper.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/settings.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/sub_communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/portal/user_page.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/all_communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/community.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/css/first_run.css (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/css/login.css (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/css/web.css (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/edit_community.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/error.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/feed.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/add-post-no-image.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/bandwidthalert.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/bandwidthlost.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/banner.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/favicon.ico (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/icons/icon-128x128.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/icons/icon-144x144.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/icons/icon-152x152.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/icons/icon-192x192.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/icons/icon-384x384.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/icons/icon-512x512.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/icons/icon-72x72.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/icons/icon-96x96.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/images/splash-background.png (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/js/admin.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/js/login.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/js/menus.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/js/painting.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/js/post.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/js/reports.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/js/web.js (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/manage_communities.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/manifest.json (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/me_page.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/moderate_user.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/new_community.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/notifications.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/assets/bin_icon.svg (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/assets/flag_icon.svg (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/assets/heart_icon.svg (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/assets/menu_icon.svg (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/assets/reply_icon.svg (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/assets/share_icon.svg (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/following_list.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/head.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/more_posts.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/nav_bar.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/new_post.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/notifications.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/post_template.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/posts_list.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/partials/report_modal.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/post.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/reports.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/robots.txt (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/user_page.ejs (100%) rename apps/juxtaposition-ui/{src => }/webfiles/web/users.ejs (100%) diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/all_communities.ejs b/apps/juxtaposition-ui/webfiles/ctr/all_communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/all_communities.ejs rename to apps/juxtaposition-ui/webfiles/ctr/all_communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/communities.ejs b/apps/juxtaposition-ui/webfiles/ctr/communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/communities.ejs rename to apps/juxtaposition-ui/webfiles/ctr/communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/community.ejs b/apps/juxtaposition-ui/webfiles/ctr/community.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/community.ejs rename to apps/juxtaposition-ui/webfiles/ctr/community.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/firstrun.css b/apps/juxtaposition-ui/webfiles/ctr/css/firstrun.css similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/css/firstrun.css rename to apps/juxtaposition-ui/webfiles/ctr/css/firstrun.css diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css b/apps/juxtaposition-ui/webfiles/ctr/css/juxt.css similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/css/juxt.css rename to apps/juxtaposition-ui/webfiles/ctr/css/juxt.css diff --git a/apps/juxtaposition-ui/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/webfiles/ctr/css/sprites.css new file mode 100644 index 00000000..d0a0366f --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/ctr/css/sprites.css @@ -0,0 +1,126 @@ + + /* This file is generated by @repo/esbuild-plugin-spritesmith */ + .sprite { + background-image: url(@/webfiles/ctr/images/sprites.png); + } + .sprite.sp-feeling-frustrated { + background-position: -0px -0px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-frustrated:checked { + background-position: -28px -0px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-happy { + background-position: -0px -28px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-happy:checked { + background-position: -28px -28px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-like { + background-position: -56px -0px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-like:checked { + background-position: -56px -28px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-normal { + background-position: -0px -56px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-normal:checked { + background-position: -28px -56px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-puzzled { + background-position: -56px -56px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-puzzled:checked { + background-position: -84px -0px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-surprise { + background-position: -84px -28px; + width: 28px; + height: 28px; + } + .sprite.sp-feeling-surprise:checked { + background-position: -84px -56px; + width: 28px; + height: 28px; + } + .sprite.sp-follower-count { + background-position: -112px -72px; + width: 19px; + height: 14px; + } + .sprite.sp-memo-input { + background-position: -112px -38px; + width: 17px; + height: 17px; + } + .sprite.sp-memo-input.selected { + background-position: -112px -55px; + width: 17px; + height: 17px; + } + .sprite.sp-post-count { + background-position: -112px -86px; + width: 16px; + height: 16px; + } + .sprite.sp-reply { + background-position: -78px -84px; + width: 16px; + height: 16px; + } + .sprite.sp-screenshot { + background-position: -0px -84px; + width: 26px; + height: 22px; + } + .sprite.sp-tag { + background-position: -0px -106px; + width: 12px; + height: 12px; + } + .sprite.sp-text-input { + background-position: -26px -84px; + width: 26px; + height: 17px; + } + .sprite.sp-text-input.selected { + background-position: -52px -84px; + width: 26px; + height: 17px; + } + .sprite.sp-yeah-small { + background-position: -94px -84px; + width: 16px; + height: 16px; + } + .sprite.sp-yeah { + background-position: -112px -0px; + width: 20px; + height: 19px; + } + .sprite.sp-yeah.selected { + background-position: -112px -19px; + width: 20px; + height: 19px; + } + \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/error.ejs b/apps/juxtaposition-ui/webfiles/ctr/error.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/error.ejs rename to apps/juxtaposition-ui/webfiles/ctr/error.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/error_fatal.ejs b/apps/juxtaposition-ui/webfiles/ctr/error_fatal.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/error_fatal.ejs rename to apps/juxtaposition-ui/webfiles/ctr/error_fatal.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/feed.ejs b/apps/juxtaposition-ui/webfiles/ctr/feed.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/feed.ejs rename to apps/juxtaposition-ui/webfiles/ctr/feed.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/guest_notice.ejs b/apps/juxtaposition-ui/webfiles/ctr/guest_notice.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/guest_notice.ejs rename to apps/juxtaposition-ui/webfiles/ctr/guest_notice.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/background.png b/apps/juxtaposition-ui/webfiles/ctr/images/background.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/background.png rename to apps/juxtaposition-ui/webfiles/ctr/images/background.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/bandwidthalert.png b/apps/juxtaposition-ui/webfiles/ctr/images/bandwidthalert.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/bandwidthalert.png rename to apps/juxtaposition-ui/webfiles/ctr/images/bandwidthalert.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/banner.png b/apps/juxtaposition-ui/webfiles/ctr/images/banner.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/banner.png rename to apps/juxtaposition-ui/webfiles/ctr/images/banner.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/header.png b/apps/juxtaposition-ui/webfiles/ctr/images/header.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/header.png rename to apps/juxtaposition-ui/webfiles/ctr/images/header.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/headline-green.png b/apps/juxtaposition-ui/webfiles/ctr/images/headline-green.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/headline-green.png rename to apps/juxtaposition-ui/webfiles/ctr/images/headline-green.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/headline.png b/apps/juxtaposition-ui/webfiles/ctr/images/headline.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/headline.png rename to apps/juxtaposition-ui/webfiles/ctr/images/headline.png diff --git a/apps/juxtaposition-ui/webfiles/ctr/images/sprites.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..de4aaac0e0739c5dce1c494ed318e0c9fd0df4fc GIT binary patch literal 14710 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 literal 0 HcmV?d00001 diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-frustrated.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-frustrated.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated_checked.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-frustrated_checked.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-frustrated_checked.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-frustrated_checked.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-happy.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-happy.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy_checked.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-happy_checked.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-happy_checked.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-happy_checked.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-like.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-like.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like_checked.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-like_checked.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-like_checked.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-like_checked.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-normal.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-normal.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-normal.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-normal.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-normal_checked.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-normal_checked.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-normal_checked.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-normal_checked.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-puzzled.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-puzzled.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-puzzled.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-puzzled.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-puzzled_checked.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-puzzled_checked.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-puzzled_checked.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-puzzled_checked.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-surprise.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-surprise.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise_checked.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-surprise_checked.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/feeling-surprise_checked.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/feeling-surprise_checked.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/follower-count.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/follower-count.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/follower-count.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/follower-count.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/memo-input.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/memo-input.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input.selected.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/memo-input.selected.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/memo-input.selected.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/memo-input.selected.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/post-count.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/post-count.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/post-count.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/post-count.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/reply.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/reply.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/reply.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/reply.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/screenshot.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/screenshot.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/screenshot.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/screenshot.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/tag.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/tag.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/tag.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/tag.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/text-input.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/text-input.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input.selected.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/text-input.selected.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/text-input.selected.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/text-input.selected.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-small.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/yeah-small.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah-small.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/yeah-small.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/yeah.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/yeah.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah.selected.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites/yeah.selected.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/sprites/yeah.selected.png rename to apps/juxtaposition-ui/webfiles/ctr/images/sprites/yeah.selected.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/images/tab.png b/apps/juxtaposition-ui/webfiles/ctr/images/tab.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/images/tab.png rename to apps/juxtaposition-ui/webfiles/ctr/images/tab.png diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/js/debug.js b/apps/juxtaposition-ui/webfiles/ctr/js/debug.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/js/debug.js rename to apps/juxtaposition-ui/webfiles/ctr/js/debug.js diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/js/firstrun.js b/apps/juxtaposition-ui/webfiles/ctr/js/firstrun.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/js/firstrun.js rename to apps/juxtaposition-ui/webfiles/ctr/js/firstrun.js diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/js/juxt.js b/apps/juxtaposition-ui/webfiles/ctr/js/juxt.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/js/juxt.js rename to apps/juxtaposition-ui/webfiles/ctr/js/juxt.js diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/js/pjax.js b/apps/juxtaposition-ui/webfiles/ctr/js/pjax.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/js/pjax.js rename to apps/juxtaposition-ui/webfiles/ctr/js/pjax.js diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/notifications.ejs b/apps/juxtaposition-ui/webfiles/ctr/notifications.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/notifications.ejs rename to apps/juxtaposition-ui/webfiles/ctr/notifications.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/following_list.ejs b/apps/juxtaposition-ui/webfiles/ctr/partials/following_list.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/partials/following_list.ejs rename to apps/juxtaposition-ui/webfiles/ctr/partials/following_list.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/head.ejs b/apps/juxtaposition-ui/webfiles/ctr/partials/head.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/partials/head.ejs rename to apps/juxtaposition-ui/webfiles/ctr/partials/head.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs b/apps/juxtaposition-ui/webfiles/ctr/partials/new_post.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/partials/new_post.ejs rename to apps/juxtaposition-ui/webfiles/ctr/partials/new_post.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/notifications.ejs b/apps/juxtaposition-ui/webfiles/ctr/partials/notifications.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/partials/notifications.ejs rename to apps/juxtaposition-ui/webfiles/ctr/partials/notifications.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/post_template.ejs b/apps/juxtaposition-ui/webfiles/ctr/partials/post_template.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/partials/post_template.ejs rename to apps/juxtaposition-ui/webfiles/ctr/partials/post_template.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/posts_list.ejs b/apps/juxtaposition-ui/webfiles/ctr/partials/posts_list.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/partials/posts_list.ejs rename to apps/juxtaposition-ui/webfiles/ctr/partials/posts_list.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/partials/requests.ejs b/apps/juxtaposition-ui/webfiles/ctr/partials/requests.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/partials/requests.ejs rename to apps/juxtaposition-ui/webfiles/ctr/partials/requests.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/post.ejs b/apps/juxtaposition-ui/webfiles/ctr/post.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/post.ejs rename to apps/juxtaposition-ui/webfiles/ctr/post.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/settings.ejs b/apps/juxtaposition-ui/webfiles/ctr/settings.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/settings.ejs rename to apps/juxtaposition-ui/webfiles/ctr/settings.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/sub_communities.ejs b/apps/juxtaposition-ui/webfiles/ctr/sub_communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/sub_communities.ejs rename to apps/juxtaposition-ui/webfiles/ctr/sub_communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/user_menu.ejs b/apps/juxtaposition-ui/webfiles/ctr/user_menu.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/user_menu.ejs rename to apps/juxtaposition-ui/webfiles/ctr/user_menu.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/ctr/user_page.ejs b/apps/juxtaposition-ui/webfiles/ctr/user_page.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/ctr/user_page.ejs rename to apps/juxtaposition-ui/webfiles/ctr/user_page.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/all_communities.ejs b/apps/juxtaposition-ui/webfiles/portal/all_communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/all_communities.ejs rename to apps/juxtaposition-ui/webfiles/portal/all_communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/communities.ejs b/apps/juxtaposition-ui/webfiles/portal/communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/communities.ejs rename to apps/juxtaposition-ui/webfiles/portal/communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/community.ejs b/apps/juxtaposition-ui/webfiles/portal/community.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/community.ejs rename to apps/juxtaposition-ui/webfiles/portal/community.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/css/firstrun.css b/apps/juxtaposition-ui/webfiles/portal/css/firstrun.css similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/css/firstrun.css rename to apps/juxtaposition-ui/webfiles/portal/css/firstrun.css diff --git a/apps/juxtaposition-ui/src/webfiles/portal/css/juxt.css b/apps/juxtaposition-ui/webfiles/portal/css/juxt.css similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/css/juxt.css rename to apps/juxtaposition-ui/webfiles/portal/css/juxt.css diff --git a/apps/juxtaposition-ui/src/webfiles/portal/error.ejs b/apps/juxtaposition-ui/webfiles/portal/error.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/error.ejs rename to apps/juxtaposition-ui/webfiles/portal/error.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/error_fatal.ejs b/apps/juxtaposition-ui/webfiles/portal/error_fatal.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/error_fatal.ejs rename to apps/juxtaposition-ui/webfiles/portal/error_fatal.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/feed.ejs b/apps/juxtaposition-ui/webfiles/portal/feed.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/feed.ejs rename to apps/juxtaposition-ui/webfiles/portal/feed.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.eot b/apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.eot similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.eot rename to apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.eot diff --git a/apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.otf b/apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.otf similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.otf rename to apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.otf diff --git a/apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.svg b/apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.svg similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.svg rename to apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.svg diff --git a/apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.ttf b/apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.ttf similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.ttf rename to apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.ttf diff --git a/apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.woff b/apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.woff similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/fonts/Poppins-Light.woff rename to apps/juxtaposition-ui/webfiles/portal/fonts/Poppins-Light.woff diff --git a/apps/juxtaposition-ui/src/webfiles/portal/guest_notice.ejs b/apps/juxtaposition-ui/webfiles/portal/guest_notice.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/guest_notice.ejs rename to apps/juxtaposition-ui/webfiles/portal/guest_notice.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/images/add-post-no-image.png b/apps/juxtaposition-ui/webfiles/portal/images/add-post-no-image.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/images/add-post-no-image.png rename to apps/juxtaposition-ui/webfiles/portal/images/add-post-no-image.png diff --git a/apps/juxtaposition-ui/src/webfiles/portal/images/background.png b/apps/juxtaposition-ui/webfiles/portal/images/background.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/images/background.png rename to apps/juxtaposition-ui/webfiles/portal/images/background.png diff --git a/apps/juxtaposition-ui/src/webfiles/portal/images/bandwidthalert.png b/apps/juxtaposition-ui/webfiles/portal/images/bandwidthalert.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/images/bandwidthalert.png rename to apps/juxtaposition-ui/webfiles/portal/images/bandwidthalert.png diff --git a/apps/juxtaposition-ui/src/webfiles/portal/images/bandwidthlost.png b/apps/juxtaposition-ui/webfiles/portal/images/bandwidthlost.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/images/bandwidthlost.png rename to apps/juxtaposition-ui/webfiles/portal/images/bandwidthlost.png diff --git a/apps/juxtaposition-ui/src/webfiles/portal/images/banner.png b/apps/juxtaposition-ui/webfiles/portal/images/banner.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/images/banner.png rename to apps/juxtaposition-ui/webfiles/portal/images/banner.png diff --git a/apps/juxtaposition-ui/src/webfiles/portal/images/favicon.ico b/apps/juxtaposition-ui/webfiles/portal/images/favicon.ico similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/images/favicon.ico rename to apps/juxtaposition-ui/webfiles/portal/images/favicon.ico diff --git a/apps/juxtaposition-ui/src/webfiles/portal/images/splash-background.png b/apps/juxtaposition-ui/webfiles/portal/images/splash-background.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/images/splash-background.png rename to apps/juxtaposition-ui/webfiles/portal/images/splash-background.png diff --git a/apps/juxtaposition-ui/src/webfiles/portal/js/debug.js b/apps/juxtaposition-ui/webfiles/portal/js/debug.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/js/debug.js rename to apps/juxtaposition-ui/webfiles/portal/js/debug.js diff --git a/apps/juxtaposition-ui/src/webfiles/portal/js/firstrun.js b/apps/juxtaposition-ui/webfiles/portal/js/firstrun.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/js/firstrun.js rename to apps/juxtaposition-ui/webfiles/portal/js/firstrun.js diff --git a/apps/juxtaposition-ui/src/webfiles/portal/js/juxt.js b/apps/juxtaposition-ui/webfiles/portal/js/juxt.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/js/juxt.js rename to apps/juxtaposition-ui/webfiles/portal/js/juxt.js diff --git a/apps/juxtaposition-ui/src/webfiles/portal/notifications.ejs b/apps/juxtaposition-ui/webfiles/portal/notifications.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/notifications.ejs rename to apps/juxtaposition-ui/webfiles/portal/notifications.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/following_list.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/following_list.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/following_list.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/following_list.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/head.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/head.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/head.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/head.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/nav_bar.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/nav_bar.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/nav_bar.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/nav_bar.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/new_post.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/new_post.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/new_post.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/new_post.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/not_ready.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/not_ready.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/not_ready.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/not_ready.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/notifications.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/notifications.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/notifications.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/notifications.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/post_template.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/post_template.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/post_template.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/post_template.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/posts_list.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/posts_list.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/posts_list.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/posts_list.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/requests.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/requests.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/requests.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/requests.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/partials/updates.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/updates.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/partials/updates.ejs rename to apps/juxtaposition-ui/webfiles/portal/partials/updates.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/post.ejs b/apps/juxtaposition-ui/webfiles/portal/post.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/post.ejs rename to apps/juxtaposition-ui/webfiles/portal/post.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/scroll_wrapper.ejs b/apps/juxtaposition-ui/webfiles/portal/scroll_wrapper.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/scroll_wrapper.ejs rename to apps/juxtaposition-ui/webfiles/portal/scroll_wrapper.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/settings.ejs b/apps/juxtaposition-ui/webfiles/portal/settings.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/settings.ejs rename to apps/juxtaposition-ui/webfiles/portal/settings.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/sub_communities.ejs b/apps/juxtaposition-ui/webfiles/portal/sub_communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/sub_communities.ejs rename to apps/juxtaposition-ui/webfiles/portal/sub_communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/portal/user_page.ejs b/apps/juxtaposition-ui/webfiles/portal/user_page.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/portal/user_page.ejs rename to apps/juxtaposition-ui/webfiles/portal/user_page.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/all_communities.ejs b/apps/juxtaposition-ui/webfiles/web/all_communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/all_communities.ejs rename to apps/juxtaposition-ui/webfiles/web/all_communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/communities.ejs b/apps/juxtaposition-ui/webfiles/web/communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/communities.ejs rename to apps/juxtaposition-ui/webfiles/web/communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/community.ejs b/apps/juxtaposition-ui/webfiles/web/community.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/community.ejs rename to apps/juxtaposition-ui/webfiles/web/community.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/css/first_run.css b/apps/juxtaposition-ui/webfiles/web/css/first_run.css similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/css/first_run.css rename to apps/juxtaposition-ui/webfiles/web/css/first_run.css diff --git a/apps/juxtaposition-ui/src/webfiles/web/css/login.css b/apps/juxtaposition-ui/webfiles/web/css/login.css similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/css/login.css rename to apps/juxtaposition-ui/webfiles/web/css/login.css diff --git a/apps/juxtaposition-ui/src/webfiles/web/css/web.css b/apps/juxtaposition-ui/webfiles/web/css/web.css similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/css/web.css rename to apps/juxtaposition-ui/webfiles/web/css/web.css diff --git a/apps/juxtaposition-ui/src/webfiles/web/edit_community.ejs b/apps/juxtaposition-ui/webfiles/web/edit_community.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/edit_community.ejs rename to apps/juxtaposition-ui/webfiles/web/edit_community.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/error.ejs b/apps/juxtaposition-ui/webfiles/web/error.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/error.ejs rename to apps/juxtaposition-ui/webfiles/web/error.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/feed.ejs b/apps/juxtaposition-ui/webfiles/web/feed.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/feed.ejs rename to apps/juxtaposition-ui/webfiles/web/feed.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/add-post-no-image.png b/apps/juxtaposition-ui/webfiles/web/images/add-post-no-image.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/add-post-no-image.png rename to apps/juxtaposition-ui/webfiles/web/images/add-post-no-image.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/bandwidthalert.png b/apps/juxtaposition-ui/webfiles/web/images/bandwidthalert.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/bandwidthalert.png rename to apps/juxtaposition-ui/webfiles/web/images/bandwidthalert.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/bandwidthlost.png b/apps/juxtaposition-ui/webfiles/web/images/bandwidthlost.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/bandwidthlost.png rename to apps/juxtaposition-ui/webfiles/web/images/bandwidthlost.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/banner.png b/apps/juxtaposition-ui/webfiles/web/images/banner.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/banner.png rename to apps/juxtaposition-ui/webfiles/web/images/banner.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/favicon.ico b/apps/juxtaposition-ui/webfiles/web/images/favicon.ico similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/favicon.ico rename to apps/juxtaposition-ui/webfiles/web/images/favicon.ico diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-128x128.png b/apps/juxtaposition-ui/webfiles/web/images/icons/icon-128x128.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-128x128.png rename to apps/juxtaposition-ui/webfiles/web/images/icons/icon-128x128.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-144x144.png b/apps/juxtaposition-ui/webfiles/web/images/icons/icon-144x144.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-144x144.png rename to apps/juxtaposition-ui/webfiles/web/images/icons/icon-144x144.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-152x152.png b/apps/juxtaposition-ui/webfiles/web/images/icons/icon-152x152.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-152x152.png rename to apps/juxtaposition-ui/webfiles/web/images/icons/icon-152x152.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-192x192.png b/apps/juxtaposition-ui/webfiles/web/images/icons/icon-192x192.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-192x192.png rename to apps/juxtaposition-ui/webfiles/web/images/icons/icon-192x192.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-384x384.png b/apps/juxtaposition-ui/webfiles/web/images/icons/icon-384x384.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-384x384.png rename to apps/juxtaposition-ui/webfiles/web/images/icons/icon-384x384.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-512x512.png b/apps/juxtaposition-ui/webfiles/web/images/icons/icon-512x512.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-512x512.png rename to apps/juxtaposition-ui/webfiles/web/images/icons/icon-512x512.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-72x72.png b/apps/juxtaposition-ui/webfiles/web/images/icons/icon-72x72.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-72x72.png rename to apps/juxtaposition-ui/webfiles/web/images/icons/icon-72x72.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-96x96.png b/apps/juxtaposition-ui/webfiles/web/images/icons/icon-96x96.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/icons/icon-96x96.png rename to apps/juxtaposition-ui/webfiles/web/images/icons/icon-96x96.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/images/splash-background.png b/apps/juxtaposition-ui/webfiles/web/images/splash-background.png similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/images/splash-background.png rename to apps/juxtaposition-ui/webfiles/web/images/splash-background.png diff --git a/apps/juxtaposition-ui/src/webfiles/web/js/admin.js b/apps/juxtaposition-ui/webfiles/web/js/admin.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/js/admin.js rename to apps/juxtaposition-ui/webfiles/web/js/admin.js diff --git a/apps/juxtaposition-ui/src/webfiles/web/js/login.js b/apps/juxtaposition-ui/webfiles/web/js/login.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/js/login.js rename to apps/juxtaposition-ui/webfiles/web/js/login.js diff --git a/apps/juxtaposition-ui/src/webfiles/web/js/menus.js b/apps/juxtaposition-ui/webfiles/web/js/menus.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/js/menus.js rename to apps/juxtaposition-ui/webfiles/web/js/menus.js diff --git a/apps/juxtaposition-ui/src/webfiles/web/js/painting.js b/apps/juxtaposition-ui/webfiles/web/js/painting.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/js/painting.js rename to apps/juxtaposition-ui/webfiles/web/js/painting.js diff --git a/apps/juxtaposition-ui/src/webfiles/web/js/post.js b/apps/juxtaposition-ui/webfiles/web/js/post.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/js/post.js rename to apps/juxtaposition-ui/webfiles/web/js/post.js diff --git a/apps/juxtaposition-ui/src/webfiles/web/js/reports.js b/apps/juxtaposition-ui/webfiles/web/js/reports.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/js/reports.js rename to apps/juxtaposition-ui/webfiles/web/js/reports.js diff --git a/apps/juxtaposition-ui/src/webfiles/web/js/web.js b/apps/juxtaposition-ui/webfiles/web/js/web.js similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/js/web.js rename to apps/juxtaposition-ui/webfiles/web/js/web.js diff --git a/apps/juxtaposition-ui/src/webfiles/web/manage_communities.ejs b/apps/juxtaposition-ui/webfiles/web/manage_communities.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/manage_communities.ejs rename to apps/juxtaposition-ui/webfiles/web/manage_communities.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/manifest.json b/apps/juxtaposition-ui/webfiles/web/manifest.json similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/manifest.json rename to apps/juxtaposition-ui/webfiles/web/manifest.json diff --git a/apps/juxtaposition-ui/src/webfiles/web/me_page.ejs b/apps/juxtaposition-ui/webfiles/web/me_page.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/me_page.ejs rename to apps/juxtaposition-ui/webfiles/web/me_page.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/moderate_user.ejs b/apps/juxtaposition-ui/webfiles/web/moderate_user.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/moderate_user.ejs rename to apps/juxtaposition-ui/webfiles/web/moderate_user.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/new_community.ejs b/apps/juxtaposition-ui/webfiles/web/new_community.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/new_community.ejs rename to apps/juxtaposition-ui/webfiles/web/new_community.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/notifications.ejs b/apps/juxtaposition-ui/webfiles/web/notifications.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/notifications.ejs rename to apps/juxtaposition-ui/webfiles/web/notifications.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/assets/bin_icon.svg b/apps/juxtaposition-ui/webfiles/web/partials/assets/bin_icon.svg similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/assets/bin_icon.svg rename to apps/juxtaposition-ui/webfiles/web/partials/assets/bin_icon.svg diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/assets/flag_icon.svg b/apps/juxtaposition-ui/webfiles/web/partials/assets/flag_icon.svg similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/assets/flag_icon.svg rename to apps/juxtaposition-ui/webfiles/web/partials/assets/flag_icon.svg diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/assets/heart_icon.svg b/apps/juxtaposition-ui/webfiles/web/partials/assets/heart_icon.svg similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/assets/heart_icon.svg rename to apps/juxtaposition-ui/webfiles/web/partials/assets/heart_icon.svg diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/assets/menu_icon.svg b/apps/juxtaposition-ui/webfiles/web/partials/assets/menu_icon.svg similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/assets/menu_icon.svg rename to apps/juxtaposition-ui/webfiles/web/partials/assets/menu_icon.svg diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/assets/reply_icon.svg b/apps/juxtaposition-ui/webfiles/web/partials/assets/reply_icon.svg similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/assets/reply_icon.svg rename to apps/juxtaposition-ui/webfiles/web/partials/assets/reply_icon.svg diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/assets/share_icon.svg b/apps/juxtaposition-ui/webfiles/web/partials/assets/share_icon.svg similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/assets/share_icon.svg rename to apps/juxtaposition-ui/webfiles/web/partials/assets/share_icon.svg diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/following_list.ejs b/apps/juxtaposition-ui/webfiles/web/partials/following_list.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/following_list.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/following_list.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/head.ejs b/apps/juxtaposition-ui/webfiles/web/partials/head.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/head.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/head.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/more_posts.ejs b/apps/juxtaposition-ui/webfiles/web/partials/more_posts.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/more_posts.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/more_posts.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/nav_bar.ejs b/apps/juxtaposition-ui/webfiles/web/partials/nav_bar.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/nav_bar.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/nav_bar.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/new_post.ejs b/apps/juxtaposition-ui/webfiles/web/partials/new_post.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/new_post.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/new_post.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/notifications.ejs b/apps/juxtaposition-ui/webfiles/web/partials/notifications.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/notifications.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/notifications.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/post_template.ejs b/apps/juxtaposition-ui/webfiles/web/partials/post_template.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/post_template.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/post_template.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/posts_list.ejs b/apps/juxtaposition-ui/webfiles/web/partials/posts_list.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/posts_list.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/posts_list.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/partials/report_modal.ejs b/apps/juxtaposition-ui/webfiles/web/partials/report_modal.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/partials/report_modal.ejs rename to apps/juxtaposition-ui/webfiles/web/partials/report_modal.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/post.ejs b/apps/juxtaposition-ui/webfiles/web/post.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/post.ejs rename to apps/juxtaposition-ui/webfiles/web/post.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/reports.ejs b/apps/juxtaposition-ui/webfiles/web/reports.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/reports.ejs rename to apps/juxtaposition-ui/webfiles/web/reports.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/robots.txt b/apps/juxtaposition-ui/webfiles/web/robots.txt similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/robots.txt rename to apps/juxtaposition-ui/webfiles/web/robots.txt diff --git a/apps/juxtaposition-ui/src/webfiles/web/user_page.ejs b/apps/juxtaposition-ui/webfiles/web/user_page.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/user_page.ejs rename to apps/juxtaposition-ui/webfiles/web/user_page.ejs diff --git a/apps/juxtaposition-ui/src/webfiles/web/users.ejs b/apps/juxtaposition-ui/webfiles/web/users.ejs similarity index 100% rename from apps/juxtaposition-ui/src/webfiles/web/users.ejs rename to apps/juxtaposition-ui/webfiles/web/users.ejs From 42fa43f22d84c228f8a3a81806f6f5b3307f2e2e Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Tue, 20 Jan 2026 17:59:26 +1100 Subject: [PATCH 087/155] fix(ctr): ignore generated spritesheet --- .gitignore | 4 +- .../webfiles/ctr/css/sprites.css | 126 ------------------ .../webfiles/ctr/images/sprites.png | Bin 14710 -> 0 bytes 3 files changed, 2 insertions(+), 128 deletions(-) delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/css/sprites.css delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/images/sprites.png diff --git a/.gitignore b/.gitignore index 5b7252b8..c5ff3199 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ uploads/ .DS_Store # auto generated files -apps/juxtaposition-ui/src/webfiles/ctr/css/sprites.css -apps/juxtaposition-ui/src/webfiles/ctr/images/sprites.png \ No newline at end of file +apps/juxtaposition-ui/webfiles/ctr/css/sprites.css +apps/juxtaposition-ui/webfiles/ctr/images/sprites.png \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/ctr/css/sprites.css b/apps/juxtaposition-ui/webfiles/ctr/css/sprites.css deleted file mode 100644 index d0a0366f..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/css/sprites.css +++ /dev/null @@ -1,126 +0,0 @@ - - /* This file is generated by @repo/esbuild-plugin-spritesmith */ - .sprite { - background-image: url(@/webfiles/ctr/images/sprites.png); - } - .sprite.sp-feeling-frustrated { - background-position: -0px -0px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-frustrated:checked { - background-position: -28px -0px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-happy { - background-position: -0px -28px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-happy:checked { - background-position: -28px -28px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-like { - background-position: -56px -0px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-like:checked { - background-position: -56px -28px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-normal { - background-position: -0px -56px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-normal:checked { - background-position: -28px -56px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-puzzled { - background-position: -56px -56px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-puzzled:checked { - background-position: -84px -0px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-surprise { - background-position: -84px -28px; - width: 28px; - height: 28px; - } - .sprite.sp-feeling-surprise:checked { - background-position: -84px -56px; - width: 28px; - height: 28px; - } - .sprite.sp-follower-count { - background-position: -112px -72px; - width: 19px; - height: 14px; - } - .sprite.sp-memo-input { - background-position: -112px -38px; - width: 17px; - height: 17px; - } - .sprite.sp-memo-input.selected { - background-position: -112px -55px; - width: 17px; - height: 17px; - } - .sprite.sp-post-count { - background-position: -112px -86px; - width: 16px; - height: 16px; - } - .sprite.sp-reply { - background-position: -78px -84px; - width: 16px; - height: 16px; - } - .sprite.sp-screenshot { - background-position: -0px -84px; - width: 26px; - height: 22px; - } - .sprite.sp-tag { - background-position: -0px -106px; - width: 12px; - height: 12px; - } - .sprite.sp-text-input { - background-position: -26px -84px; - width: 26px; - height: 17px; - } - .sprite.sp-text-input.selected { - background-position: -52px -84px; - width: 26px; - height: 17px; - } - .sprite.sp-yeah-small { - background-position: -94px -84px; - width: 16px; - height: 16px; - } - .sprite.sp-yeah { - background-position: -112px -0px; - width: 20px; - height: 19px; - } - .sprite.sp-yeah.selected { - background-position: -112px -19px; - width: 20px; - height: 19px; - } - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/ctr/images/sprites.png b/apps/juxtaposition-ui/webfiles/ctr/images/sprites.png deleted file mode 100644 index de4aaac0e0739c5dce1c494ed318e0c9fd0df4fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14710 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 From 928368367c49b0189f72225d689fcf119938323d Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Tue, 20 Jan 2026 18:00:25 +1100 Subject: [PATCH 088/155] chore: update nodemon for rename --- apps/juxtaposition-ui/nodemon.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/juxtaposition-ui/nodemon.json b/apps/juxtaposition-ui/nodemon.json index 5b610579..cbd8b440 100644 --- a/apps/juxtaposition-ui/nodemon.json +++ b/apps/juxtaposition-ui/nodemon.json @@ -1,11 +1,12 @@ { "watch": [ - "src/" + "src/", + "webfiles" ], "ext": "js,ts,ejs,tsx,jsx,json,css", "ignore": [ - "src/webfiles/ctr/css/sprites.css", - "src/webfiles/ctr/images/sprites.png" + "webfiles/ctr/css/sprites.css", + "webfiles/ctr/images/sprites.png" ], "env": { "NODE_ENV": "development" From 61d2079de93b9a70c799e759d048be5440916947 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Tue, 20 Jan 2026 18:05:40 +1100 Subject: [PATCH 089/155] chore: update build for webfiles rename --- apps/juxtaposition-ui/eslint.config.mjs | 6 ++-- apps/juxtaposition-ui/tsup.config.ts | 40 ++++++++++++------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/juxtaposition-ui/eslint.config.mjs b/apps/juxtaposition-ui/eslint.config.mjs index ebf67b40..a425e6f2 100644 --- a/apps/juxtaposition-ui/eslint.config.mjs +++ b/apps/juxtaposition-ui/eslint.config.mjs @@ -15,11 +15,11 @@ export default defineConfig([ rules: { 'no-restricted-imports': 'off' // It's a special compile step, we can't use path aliases }, - files: ['src/webfiles/**/*.js'] + files: ['webfiles/**/*.js'] }, { // Rules that apply to the 3DS (CTR) and Wii U (Portal) browsers - files: ['src/webfiles/ctr/**/*.js', 'src/webfiles/portal/**/*.js'], + files: ['webfiles/ctr/**/*.js', 'webfiles/portal/**/*.js'], rules: { 'no-var': 'off' // 3DS and Wii U browsers need to use var }, @@ -46,6 +46,6 @@ export default defineConfig([ ...globals.builtin } }, - ignores: ['src/webfiles/**/*.js'] + ignores: ['webfiles/**/*.js'] } ]); diff --git a/apps/juxtaposition-ui/tsup.config.ts b/apps/juxtaposition-ui/tsup.config.ts index 2871a76e..cc611e7c 100644 --- a/apps/juxtaposition-ui/tsup.config.ts +++ b/apps/juxtaposition-ui/tsup.config.ts @@ -39,10 +39,10 @@ export default defineConfig([ /* CTR/3DS webfiles (Chrome 4) */ { entry: [ - 'src/webfiles/ctr/js/juxt.js', - 'src/webfiles/ctr/js/debug.js', - 'src/webfiles/ctr/js/firstrun.js', - 'src/webfiles/ctr/css/juxt.css' + 'webfiles/ctr/js/juxt.js', + 'webfiles/ctr/js/debug.js', + 'webfiles/ctr/js/firstrun.js', + 'webfiles/ctr/css/juxt.css' ], bundle: true, sourcemap: true, @@ -60,24 +60,24 @@ export default defineConfig([ esbuildPlugins: [ oxipng({ loader: 'dataurl' }), spritesmith({ - input_folder: './src/webfiles/ctr/images/sprites/', - output_css: './src/webfiles/ctr/css/sprites.css', - output_image: './src/webfiles/ctr/images/sprites.png', + input_folder: './webfiles/ctr/images/sprites/', + output_css: './webfiles/ctr/css/sprites.css', + output_image: './webfiles/ctr/images/sprites.png', output_image_url: '@/webfiles/ctr/images/sprites.png' }), copy({ resolveFrom: 'cwd', assets: [ { - from: ['./src/webfiles/ctr/**/*.ejs'], + from: ['./webfiles/ctr/**/*.ejs'], to: ['./dist/webfiles/ctr'] }, { - from: ['./src/webfiles/ctr/images/**'], + from: ['./webfiles/ctr/images/**'], to: ['./dist/webfiles/ctr/images'] }, { - from: ['./src/webfiles/ctr/fonts/**'], + from: ['./webfiles/ctr/fonts/**'], to: ['./dist/webfiles/ctr/fonts'] } ] @@ -86,7 +86,7 @@ export default defineConfig([ }, /* Portal/Wii U webfiles (~Chrome 20) */ { - entry: ['src/webfiles/portal/**/*.js', 'src/webfiles/portal/**/*.css'], + entry: ['webfiles/portal/**/*.js', 'webfiles/portal/**/*.css'], bundle: true, sourcemap: true, minify: true, @@ -105,15 +105,15 @@ export default defineConfig([ resolveFrom: 'cwd', assets: [ { - from: ['./src/webfiles/portal/**/*.ejs'], + from: ['./webfiles/portal/**/*.ejs'], to: ['./dist/webfiles/portal'] }, { - from: ['./src/webfiles/portal/images/**'], + from: ['./webfiles/portal/images/**'], to: ['./dist/webfiles/portal/images'] }, { - from: ['./src/webfiles/portal/fonts/**'], + from: ['./webfiles/portal/fonts/**'], to: ['./dist/webfiles/portal/fonts'] } ] @@ -122,7 +122,7 @@ export default defineConfig([ }, /* Main web frontend (Modern browsers) */ { - entry: ['src/webfiles/web/**/*.js', 'src/webfiles/web/**/*.css'], + entry: ['webfiles/web/**/*.js', 'webfiles/web/**/*.css'], bundle: true, sourcemap: true, minify: true, @@ -141,23 +141,23 @@ export default defineConfig([ resolveFrom: 'cwd', assets: [ { - from: ['./src/webfiles/web/**/*.ejs'], + from: ['./webfiles/web/**/*.ejs'], to: ['./dist/webfiles/web'] }, { - from: ['./src/webfiles/web/partials/assets/**'], + from: ['./webfiles/web/partials/assets/**'], to: ['./dist/webfiles/web/partials/assets'] }, { - from: ['./src/webfiles/web/images/**'], + from: ['./webfiles/web/images/**'], to: ['./dist/webfiles/web/images'] }, { - from: ['./src/webfiles/web/robots.txt'], + from: ['./webfiles/web/robots.txt'], to: ['./dist/webfiles/web/robots.txt'] }, { - from: ['./src/webfiles/web/manifest.json'], + from: ['./webfiles/web/manifest.json'], to: ['./dist/webfiles/web/manifest.json'] } ] From 3fd4f82c6e1558a60fbc184cec853c16dc03dee9 Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Tue, 20 Jan 2026 18:07:35 +1100 Subject: [PATCH 090/155] fix(ui): Move icon assets to view folder --- .../services/juxt-web/views/web/assets/bin_icon.svg | 1 + .../services/juxt-web/views/web/assets/flag_icon.svg | 1 + .../juxt-web/views/web/assets/heart_icon.svg | 6 ++++++ .../services/juxt-web/views/web/assets/menu_icon.svg | 1 + .../juxt-web/views/web/assets/reply_icon.svg | 1 + .../juxt-web/views/web/assets/share_icon.svg | 1 + .../src/services/juxt-web/views/web/icons.tsx | 12 ++++++------ 7 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/bin_icon.svg create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/flag_icon.svg create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/heart_icon.svg create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/menu_icon.svg create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/reply_icon.svg create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/share_icon.svg diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/bin_icon.svg b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/bin_icon.svg new file mode 100644 index 00000000..33a5be4c --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/bin_icon.svg @@ -0,0 +1 @@ + diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/flag_icon.svg b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/flag_icon.svg new file mode 100644 index 00000000..dd184d02 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/flag_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/heart_icon.svg b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/heart_icon.svg new file mode 100644 index 00000000..5f0d54cc --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/heart_icon.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/menu_icon.svg b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/menu_icon.svg new file mode 100644 index 00000000..eed5ce0b --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/menu_icon.svg @@ -0,0 +1 @@ + diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/reply_icon.svg b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/reply_icon.svg new file mode 100644 index 00000000..b633a269 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/reply_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/share_icon.svg b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/share_icon.svg new file mode 100644 index 00000000..8e5338a6 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/assets/share_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/icons.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/icons.tsx index 8786ff44..60f68f96 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/web/icons.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/icons.tsx @@ -1,11 +1,11 @@ /* eslint-disable no-restricted-imports -- raw import plugin does not support path aliases */ import { InlineStyle } from '@/services/juxt-web/views/common'; -import heartIcon from '../../../../webfiles/web/partials/assets/heart_icon.svg?raw'; -import replyIcon from '../../../../webfiles/web/partials/assets/reply_icon.svg?raw'; -import menuIcon from '../../../../webfiles/web/partials/assets/menu_icon.svg?raw'; -import flagIcon from '../../../../webfiles/web/partials/assets/flag_icon.svg?raw'; -import binIcon from '../../../../webfiles/web/partials/assets/bin_icon.svg?raw'; -import shareIcon from '../../../../webfiles/web/partials/assets/share_icon.svg?raw'; +import heartIcon from './assets/heart_icon.svg?raw'; +import replyIcon from './assets/reply_icon.svg?raw'; +import menuIcon from './assets/menu_icon.svg?raw'; +import flagIcon from './assets/flag_icon.svg?raw'; +import binIcon from './assets/bin_icon.svg?raw'; +import shareIcon from './assets/share_icon.svg?raw'; import type { ReactNode } from 'react'; const icons = { From 1d28dd216aee232558f34cd768c35403bcc51d4c Mon Sep 17 00:00:00 2001 From: Ash Logan Date: Tue, 20 Jan 2026 18:18:50 +1100 Subject: [PATCH 091/155] feat(ui): give each target its own typescript context this prevents them polluting each other and lets target-specific content have a nicer path alias --- apps/juxtaposition-ui/nodemon.json | 2 +- apps/juxtaposition-ui/tsup.config.ts | 5 +++- .../webfiles/ctr/css/juxt.css | 10 +++---- .../webfiles/ctr/tsconfig.json | 27 +++++++++++++++++++ .../webfiles/portal/tsconfig.json | 27 +++++++++++++++++++ .../webfiles/web/tsconfig.json | 27 +++++++++++++++++++ 6 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 apps/juxtaposition-ui/webfiles/ctr/tsconfig.json create mode 100644 apps/juxtaposition-ui/webfiles/portal/tsconfig.json create mode 100644 apps/juxtaposition-ui/webfiles/web/tsconfig.json diff --git a/apps/juxtaposition-ui/nodemon.json b/apps/juxtaposition-ui/nodemon.json index cbd8b440..c97819e9 100644 --- a/apps/juxtaposition-ui/nodemon.json +++ b/apps/juxtaposition-ui/nodemon.json @@ -1,7 +1,7 @@ { "watch": [ "src/", - "webfiles" + "webfiles/" ], "ext": "js,ts,ejs,tsx,jsx,json,css", "ignore": [ diff --git a/apps/juxtaposition-ui/tsup.config.ts b/apps/juxtaposition-ui/tsup.config.ts index cc611e7c..1cf193bc 100644 --- a/apps/juxtaposition-ui/tsup.config.ts +++ b/apps/juxtaposition-ui/tsup.config.ts @@ -56,6 +56,7 @@ export default defineConfig([ esbuildOptions(options): void { options.external = ['/fonts/*']; + options.tsconfig = './webfiles/ctr/tsconfig.json'; }, esbuildPlugins: [ oxipng({ loader: 'dataurl' }), @@ -63,7 +64,7 @@ export default defineConfig([ input_folder: './webfiles/ctr/images/sprites/', output_css: './webfiles/ctr/css/sprites.css', output_image: './webfiles/ctr/images/sprites.png', - output_image_url: '@/webfiles/ctr/images/sprites.png' + output_image_url: '@/images/sprites.png' }), copy({ resolveFrom: 'cwd', @@ -99,6 +100,7 @@ export default defineConfig([ esbuildOptions(options): void { options.external = ['/images/*', '/fonts/*']; + options.tsconfig = './webfiles/portal/tsconfig.json'; }, esbuildPlugins: [ copy({ @@ -135,6 +137,7 @@ export default defineConfig([ esbuildOptions(options): void { options.external = ['/images/*']; + options.tsconfig = './webfiles/web/tsconfig.json'; }, esbuildPlugins: [ copy({ diff --git a/apps/juxtaposition-ui/webfiles/ctr/css/juxt.css b/apps/juxtaposition-ui/webfiles/ctr/css/juxt.css index 164f5359..9cd58158 100644 --- a/apps/juxtaposition-ui/webfiles/ctr/css/juxt.css +++ b/apps/juxtaposition-ui/webfiles/ctr/css/juxt.css @@ -14,7 +14,7 @@ body { } #body { - background-image: url(@/webfiles/ctr/images/background.png); + background-image: url(@/images/background.png); background-size: 100px; background-repeat: repeat; padding-bottom: 30px @@ -56,7 +56,7 @@ ul { display: block; width: 400px; height: 220px; - background: url(@/webfiles/ctr/images/header.png); + background: url(@/images/header.png); background-size: 400px; position: relative; margin-bottom: 10px @@ -129,13 +129,13 @@ menu.tab-header { .headline { line-height: 2; color: #fff; - background: url(@/webfiles/ctr/images/headline.png) 0 0 no-repeat; + background: url(@/images/headline.png) 0 0 no-repeat; margin: 6px 10px; background-size: 100% !important } .headline.headline-green { - background: url(@/webfiles/ctr/images/headline-green.png) 0 0 no-repeat; + background: url(@/images/headline-green.png) 0 0 no-repeat; border-color: #59c9a5 } @@ -234,7 +234,7 @@ menu.tab-header { menu.tab-header li { display: inline-block; width: 144px; - background: url(@/webfiles/ctr/images/tab.png); + background: url(@/images/tab.png); } menu.tab-header li:first-child { diff --git a/apps/juxtaposition-ui/webfiles/ctr/tsconfig.json b/apps/juxtaposition-ui/webfiles/ctr/tsconfig.json new file mode 100644 index 00000000..f4049aa7 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/ctr/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "module": "ES2022", + "esModuleInterop": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "baseUrl": ".", + "outDir": "dist", + "allowJs": true, + "target": "es2022", + "noEmitOnError": true, + "noImplicitAny": true, + "strictPropertyInitialization": true, + "verbatimModuleSyntax": true, + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "js" + ] +} \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/tsconfig.json b/apps/juxtaposition-ui/webfiles/portal/tsconfig.json new file mode 100644 index 00000000..f4049aa7 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/portal/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "module": "ES2022", + "esModuleInterop": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "baseUrl": ".", + "outDir": "dist", + "allowJs": true, + "target": "es2022", + "noEmitOnError": true, + "noImplicitAny": true, + "strictPropertyInitialization": true, + "verbatimModuleSyntax": true, + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "js" + ] +} \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/web/tsconfig.json b/apps/juxtaposition-ui/webfiles/web/tsconfig.json new file mode 100644 index 00000000..f4049aa7 --- /dev/null +++ b/apps/juxtaposition-ui/webfiles/web/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "module": "ES2022", + "esModuleInterop": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "baseUrl": ".", + "outDir": "dist", + "allowJs": true, + "target": "es2022", + "noEmitOnError": true, + "noImplicitAny": true, + "strictPropertyInitialization": true, + "verbatimModuleSyntax": true, + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "js" + ] +} \ No newline at end of file From 0e0ad7d87c6e65fa697940ffdc9a13fa85cba425 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:37:44 +0000 Subject: [PATCH 092/155] chore(deps): bump @pretendonetwork/grpc from 2.3.5 to 2.4.1 Bumps [@pretendonetwork/grpc](https://github.com/PretendoNetwork/grpc) from 2.3.5 to 2.4.1. - [Release notes](https://github.com/PretendoNetwork/grpc/releases) - [Commits](https://github.com/PretendoNetwork/grpc/compare/2.3.5...2.4.1) --- updated-dependencies: - dependency-name: "@pretendonetwork/grpc" dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/juxtaposition-ui/package.json | 2 +- apps/miiverse-api/package.json | 2 +- package-lock.json | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/juxtaposition-ui/package.json b/apps/juxtaposition-ui/package.json index 642d814f..60f30d1f 100644 --- a/apps/juxtaposition-ui/package.json +++ b/apps/juxtaposition-ui/package.json @@ -20,7 +20,7 @@ "@aws-sdk/client-s3": "^3.958.0", "@imagemagick/magick-wasm": "^0.0.37", "@neato/config": "^4.1.0", - "@pretendonetwork/grpc": "^2.3.5", + "@pretendonetwork/grpc": "^2.4.1", "@repo/grpc-client": "^0.0.0", "classnames": "^2.5.1", "colors": "^1.4.0", diff --git a/apps/miiverse-api/package.json b/apps/miiverse-api/package.json index ae98c5a0..c006d208 100644 --- a/apps/miiverse-api/package.json +++ b/apps/miiverse-api/package.json @@ -17,7 +17,7 @@ "@aws-sdk/client-s3": "^3.958.0", "@imagemagick/magick-wasm": "^0.0.37", "@neato/config": "^4.1.0", - "@pretendonetwork/grpc": "^2.3.5", + "@pretendonetwork/grpc": "^2.4.1", "@repo/grpc-client": "^0.0.0", "colors": "^1.4.0", "crc": "^4.3.2", diff --git a/package-lock.json b/package-lock.json index ca1f15eb..45704aff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@aws-sdk/client-s3": "^3.958.0", "@imagemagick/magick-wasm": "^0.0.37", "@neato/config": "^4.1.0", - "@pretendonetwork/grpc": "^2.3.5", + "@pretendonetwork/grpc": "^2.4.1", "@repo/grpc-client": "^0.0.0", "classnames": "^2.5.1", "colors": "^1.4.0", @@ -85,7 +85,7 @@ "@aws-sdk/client-s3": "^3.958.0", "@imagemagick/magick-wasm": "^0.0.37", "@neato/config": "^4.1.0", - "@pretendonetwork/grpc": "^2.3.5", + "@pretendonetwork/grpc": "^2.4.1", "@repo/grpc-client": "^0.0.0", "colors": "^1.4.0", "crc": "^4.3.2", @@ -2308,9 +2308,9 @@ } }, "node_modules/@pretendonetwork/grpc": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@pretendonetwork/grpc/-/grpc-2.3.5.tgz", - "integrity": "sha512-FU0uvhZr8gFgiIi+gBtD6+5or34bHcd9X+ZVpRdc7IiupW5V+uxiXigJKX4Vd46QC412y+BEGofCjM1bBlTJhg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@pretendonetwork/grpc/-/grpc-2.4.1.tgz", + "integrity": "sha512-5odC6eT5czKrHJkjYPSs5CRiSvwdOaO0K8JvxsVeGD5A+LAQePeG9U8Lm3YHAg1o2tX9Vk2aCRh7KiHjs8qkrQ==", "license": "AGPL-3.0-only", "dependencies": { "@bufbuild/protobuf": "^2.2.2", From a5b16a1be224f21c3b5c9bc863ba896c76d4e3ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:37:56 +0000 Subject: [PATCH 093/155] chore(deps): bump supertest from 7.1.4 to 7.2.2 Bumps [supertest](https://github.com/ladjs/supertest) from 7.1.4 to 7.2.2. - [Release notes](https://github.com/ladjs/supertest/releases) - [Commits](https://github.com/ladjs/supertest/compare/v7.1.4...v7.2.2) --- updated-dependencies: - dependency-name: supertest dependency-version: 7.2.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/miiverse-api/package.json | 2 +- package-lock.json | 57 ++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/apps/miiverse-api/package.json b/apps/miiverse-api/package.json index ae98c5a0..277a1db3 100644 --- a/apps/miiverse-api/package.json +++ b/apps/miiverse-api/package.json @@ -36,7 +36,7 @@ "pino-http": "^11.0.0", "pino-pretty": "^13.1.3", "prom-client": "^15.1.3", - "supertest": "^7.1.4", + "supertest": "^7.2.2", "xmlbuilder": "^15.1.1", "zod": "^4.1.13" }, diff --git a/package-lock.json b/package-lock.json index ca1f15eb..8830bc2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,7 +104,7 @@ "pino-http": "^11.0.0", "pino-pretty": "^13.1.3", "prom-client": "^15.1.3", - "supertest": "^7.1.4", + "supertest": "^7.2.2", "xmlbuilder": "^15.1.1", "zod": "^4.1.13" }, @@ -2214,9 +2214,9 @@ } }, "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "license": "MIT", "dependencies": { "@noble/hashes": "^1.1.5" @@ -7137,9 +7137,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -12312,20 +12312,20 @@ } }, "node_modules/superagent": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", - "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "license": "MIT", "dependencies": { "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.4", + "form-data": "^4.0.5", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.2" + "qs": "^6.14.1" }, "engines": { "node": ">=14.18.0" @@ -12343,19 +12343,44 @@ "node": ">=4.0.0" } }, + "node_modules/superagent/node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/supertest": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", - "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "license": "MIT", "dependencies": { + "cookie-signature": "^1.2.2", "methods": "^1.1.2", - "superagent": "^10.2.3" + "superagent": "^10.3.0" }, "engines": { "node": ">=14.18.0" } }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", From 8eca7d22bfeccc381e1852130fcc664243c244c1 Mon Sep 17 00:00:00 2001 From: Nathan Tool Date: Mon, 19 Jan 2026 17:49:46 +0100 Subject: [PATCH 094/155] locales(update): Updated Catalan locale --- apps/juxtaposition-ui/src/translations/ca.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/juxtaposition-ui/src/translations/ca.json b/apps/juxtaposition-ui/src/translations/ca.json index 12c23740..0ff617a6 100644 --- a/apps/juxtaposition-ui/src/translations/ca.json +++ b/apps/juxtaposition-ui/src/translations/ca.json @@ -1,7 +1,7 @@ { "global": { "communities": "Comunitats", - "more": "Carregar més publicacions", + "more": "Carrega més publicacions", "next": "Següent", "messages": "Missatges", "activity_feed": "Taxes d'activitat", @@ -11,7 +11,7 @@ "save": "Desa", "no_posts": "No hi ha publicacions", "private": "Privat", - "close": "Tancar", + "close": "Tanca", "user_page": "Pàgina d'usuari", "go_back": "Torna", "yeahs": "Mola" From 2e95211e122a21ef9aa14fd52ba7b36fb3655abd Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 20 Jan 2026 20:34:49 +0100 Subject: [PATCH 095/155] feat: port communities router to TS --- .../{communities.js => communities.tsx} | 210 +++++++++++------- 1 file changed, 135 insertions(+), 75 deletions(-) rename apps/juxtaposition-ui/src/services/juxt-web/routes/console/{communities.js => communities.tsx} (50%) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx similarity index 50% rename from apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.js rename to apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx index cfaf5ec4..0ae04743 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx @@ -1,40 +1,26 @@ import express from 'express'; import moment from 'moment'; import multer from 'multer'; +import { z } from 'zod'; import { config } from '@/config'; import { database } from '@/database'; import { COMMUNITY } from '@/models/communities'; import { POST } from '@/models/post'; -import { redisGet, redisSet } from '@/redisCache'; +import { redisGet, redisRemove, redisSet } from '@/redisCache'; import { getCommunityHash } from '@/util'; +import { parseReq } from '@/services/juxt-web/routes/routeUtils'; +import type { InferSchemaType } from 'mongoose'; +import type { CommunitySchema } from '@/models/communities'; const upload = multer({ dest: 'uploads/' }); export const communitiesRouter = express.Router(); communitiesRouter.get('/', async function (req, res) { - const newCommunities = JSON.parse(await redisGet('newCommunities')) || await database.getNewCommunities(6); - let popularCommunities = JSON.parse(await redisGet('popularCommunities')); - - if (!popularCommunities) { - const last24Hours = await calculateMostPopularCommunities(); - popularCommunities = await COMMUNITY.aggregate([ - { $match: { olive_community_id: { $in: last24Hours }, parent: null } }, - { - $addFields: { - index: { $indexOfArray: [last24Hours, '$olive_community_id'] } - } - }, - { $sort: { index: 1 } }, - { $limit: 9 }, - { $project: { index: 0, _id: 0 } } - ]); - redisSet('popularCommunities', JSON.stringify(popularCommunities), 60 * 60); - redisSet('newCommunities', JSON.stringify(newCommunities), 60 * 60); - } + const communityStats = await getCommunityStats(); res.render(req.directory + '/communities.ejs', { cache: true, - popularCommunities: popularCommunities, - newCommunities: newCommunities + popularCommunities: communityStats.popular, + newCommunities: communityStats.new }); }); @@ -46,30 +32,45 @@ communitiesRouter.get('/all', async function (req, res) { }); communitiesRouter.get('/:communityID', async function (req, res) { - if (req.query.title_id) { - const community = await database.getCommunityByTitleID(req.query.title_id); + const { query, params } = parseReq(req, { + query: z.object({ + title_id: z.string().optional() + }), + params: z.object({ + communityID: z.string() + }) + }); + + if (query.title_id) { + const community = await database.getCommunityByTitleID(query.title_id); if (!community) { return res.redirect('/404'); } return res.redirect(`/titles/${community.olive_community_id}/new`); } - res.redirect(`/titles/${req.params.communityID}/new`); + res.redirect(`/titles/${params.communityID}/new`); }); communitiesRouter.get('/:communityID/related', async function (req, res) { - const userSettings = await database.getUserSettings(req.pid); - const userContent = await database.getUserContent(req.pid); + const { params, auth } = parseReq(req, { + params: z.object({ + communityID: z.string() + }) + }); + + const userSettings = await database.getUserSettings(auth().pid); + const userContent = await database.getUserContent(auth().pid); if (!userContent || !userSettings) { return res.redirect('/404'); } - const community = await database.getCommunityByID(req.params.communityID.toString()); + const community = await database.getCommunityByID(params.communityID); if (!community) { return res.renderError({ code: 404, message: 'Community not Found' }); } - const communityMap = await getCommunityHash(); + const communityMap = getCommunityHash(); const children = await database.getSubCommunities(community.olive_community_id); if (!children) { return res.redirect(`/titles/${community.olive_community_id}/new`); @@ -84,12 +85,22 @@ communitiesRouter.get('/:communityID/related', async function (req, res) { }); communitiesRouter.get('/:communityID/:type', async function (req, res) { - const userSettings = await database.getUserSettings(req.pid); - const userContent = await database.getUserContent(req.pid); + const { query, params, auth } = parseReq(req, { + params: z.object({ + communityID: z.string(), + type: z.string() + }), + query: z.object({ + pjax: z.stringbool().optional() + }) + }); + + const userSettings = await database.getUserSettings(auth().pid); + const userContent = await database.getUserContent(auth().pid); if (!userContent || !userSettings) { return res.redirect('/404'); } - const community = await database.getCommunityByID(req.params.communityID.toString()); + const community = await database.getCommunityByID(params.communityID); if (!community) { return res.renderError({ code: 404, @@ -106,18 +117,18 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { }; await community.save(); } - const communityMap = await getCommunityHash(); - let children = await database.getSubCommunities(community.olive_community_id); - if (children.length === 0) { + const communityMap = getCommunityHash(); + let children: InferSchemaType[] | null = await database.getSubCommunities(community.olive_community_id); + if (children !== null && children.length === 0) { children = null; } let posts; let type; - if (req.params.type === 'hot') { + if (params.type === 'hot') { posts = await database.getNumberPopularCommunityPostsByID(community, config.postLimit); type = 1; - } else if (req.params.type === 'verified') { + } else if (params.type === 'verified') { posts = await database.getNumberVerifiedCommunityPostsByID(community, config.postLimit); type = 2; } else { @@ -132,10 +143,10 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { numPosts, communityMap, userContent, - link: `/titles/${req.params.communityID}/${req.params.type}/more?offset=${posts.length}&pjax=true` + link: `/titles/${params.communityID}/${params.type}/more?offset=${posts.length}&pjax=true` }; - if (req.query.pjax) { + if (query.pjax) { return res.render(req.directory + '/partials/posts_list.ejs', { bundle, moment @@ -151,7 +162,7 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { totalNumPosts: numPosts, userSettings: userSettings, userContent: userContent, - pnid: req.user, + pnid: auth().user, children, type, bundle, @@ -160,18 +171,25 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { }); communitiesRouter.get('/:communityID/:type/more', async function (req, res) { - let offset = parseInt(req.query.offset); - const userContent = await database.getUserContent(req.pid); - const communityMap = await getCommunityHash(); + const { query, params, auth } = parseReq(req, { + params: z.object({ + communityID: z.string(), + type: z.string() + }), + query: z.object({ + offset: z.coerce.number().optional().default(0) + }) + }); + + const offset = query.offset; + const userContent = await database.getUserContent(auth().pid); + const communityMap = getCommunityHash(); let posts; - const community = await database.getCommunityByID(req.params.communityID); + const community = await database.getCommunityByID(params.communityID); if (!community) { return res.redirect('/404'); } - if (!offset) { - offset = 0; - } - switch (req.params.type) { + switch (params.type) { case 'hot': posts = await database.getNumberPopularCommunityPostsByID(community, config.postLimit, offset); break; @@ -189,7 +207,7 @@ communitiesRouter.get('/:communityID/:type/more', async function (req, res) { numPosts: posts.length, communityMap, userContent, - link: `/titles/${req.params.communityID}/${req.params.type}/more?offset=${offset + posts.length}&pjax=true` + link: `/titles/${params.communityID}/${params.type}/more?offset=${offset + posts.length}&pjax=true` }; if (posts.length > 0) { @@ -205,44 +223,86 @@ communitiesRouter.get('/:communityID/:type/more', async function (req, res) { }); communitiesRouter.post('/follow', upload.none(), async function (req, res) { - const community = await database.getCommunityByID(req.body.id); - const userContent = await database.getUserContent(req.pid); - const popularCommunities = JSON.parse(await redisGet('popularCommunities')); - let updated = false; - - if (userContent !== null && userContent.followed_communities.indexOf(community.olive_community_id) === -1) { - community.upFollower(); - userContent.addToCommunities(community.olive_community_id); - res.send({ status: 200, id: community.olive_community_id, count: community.followers }); - updated = true; - } else if (userContent !== null && userContent.followed_communities.indexOf(community.olive_community_id) !== -1) { - community.downFollower(); - userContent.removeFromCommunities(community.olive_community_id); - res.send({ status: 200, id: community.olive_community_id, count: community.followers }); - updated = true; - } else { - res.send({ status: 423, id: community.olive_community_id, count: community.followers }); + const { body, auth } = parseReq(req, { + body: z.object({ + id: z.string() + }) + }); + + const community = await database.getCommunityByID(body.id); + const userContent = await database.getUserContent(auth().pid); + + if (!userContent || !community) { + res.send({ status: 423, id: community?.olive_community_id, count: community?.followers }); + return; } - if (popularCommunities && updated) { - const index = popularCommunities.findIndex(element => element.olive_community_id === community.olive_community_id); - if (index !== -1) { - popularCommunities[index].followers = community.followers; - redisSet('popularCommunities', JSON.stringify(popularCommunities), 60 * 60); - } + // Pretty terrible use of `any` here, but database models aren't typed yet so I have to + const userFollowsCommunity = userContent.followed_communities.includes(community.olive_community_id); + if (!userFollowsCommunity) { + (community as any).upFollower(); + (userContent as any).addToCommunities(community.olive_community_id); + } else { + (community as any).downFollower(); + (userContent as any).removeFromCommunities(community.olive_community_id); } + + await redisRemove('popularCommunities'); // Force cache to refresh + res.send({ status: 200, id: community.olive_community_id, count: community.followers }); }); -async function calculateMostPopularCommunities() { +type CommunityStats = { + popular: InferSchemaType[]; + new: InferSchemaType[]; +}; + +async function getCommunityStats(): Promise { + const cachedRedisPopularCommunities = await redisGet('popularCommunities'); + let popularCommunities: InferSchemaType[] | null = cachedRedisPopularCommunities ? JSON.parse(cachedRedisPopularCommunities) : null; + if (popularCommunities == null) { + const last24Hours = await calculateMostPopularCommunities(); + popularCommunities = await COMMUNITY.aggregate([ + { $match: { olive_community_id: { $in: last24Hours }, parent: null } }, + { + $addFields: { + index: { $indexOfArray: [last24Hours, '$olive_community_id'] } + } + }, + { $sort: { index: 1 } }, + { $limit: 9 }, + { $project: { index: 0, _id: 0 } } + ]); + await redisSet('popularCommunities', JSON.stringify(popularCommunities), 60 * 60); + } + + const cachedRedisNewCommunities = await redisGet('newCommunities'); + let newCommunities: InferSchemaType[] = cachedRedisNewCommunities ? JSON.parse(cachedRedisNewCommunities) : null; + if (newCommunities == null) { + newCommunities = await database.getNewCommunities(6); + await redisSet('newCommunities', JSON.stringify(newCommunities), 60 * 60); + } + + return { + popular: popularCommunities, + new: newCommunities + }; +} + +async function calculateMostPopularCommunities(): Promise { const now = new Date(); const last24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000); const posts = await POST.find({ created_at: { $gte: last24Hours }, message_to_pid: null }).lean(); - const communityIds = {}; + const communityIds: Record = {}; for (const post of posts) { const communityId = post.community_id; - communityIds[communityId] = (communityIds[communityId] || 0) + 1; + if (!communityId) { + continue; + } + + communityIds[communityId] ??= 0; + communityIds[communityId] += 1; } return Object.entries(communityIds) .sort((a, b) => b[1] - a[1]) From 689e39dc2881c4d904f9fd89629ce2d6e663c56a Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 20 Jan 2026 20:45:43 +0100 Subject: [PATCH 096/155] feat: port notifications router to TS --- .../{notifications.js => notifications.tsx} | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) rename apps/juxtaposition-ui/src/services/juxt-web/routes/console/{notifications.js => notifications.tsx} (62%) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.tsx similarity index 62% rename from apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.js rename to apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.tsx index f48c7528..35c61f73 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/notifications.tsx @@ -1,18 +1,31 @@ import express from 'express'; import moment from 'moment'; +import { z } from 'zod'; import { database } from '@/database'; import { getUserHash, getUserFriendRequestsIncoming } from '@/util'; +import { parseReq } from '@/services/juxt-web/routes/routeUtils'; export const notificationRouter = express.Router(); notificationRouter.get('/my_news', async function (req, res) { - const notifications = await database.getNotifications(req.pid, 25, 0); + const { query, auth } = parseReq(req, { + query: z.object({ + pjax: z.stringbool().optional() + }) + }); + + const notifications = await database.getNotifications(auth().pid, 25, 0); const userMap = getUserHash(); const bundle = { notifications, userMap }; - if (req.query.pjax) { + for (const notif of notifications.filter(noti => noti.read === false)) { + // Pretty terrible use of `any` here, but database models aren't typed yet so I have to + await (notif as any).markRead(); + } + + if (query.pjax) { return res.render(req.directory + '/partials/notifications.ejs', { bundle, moment @@ -25,13 +38,16 @@ notificationRouter.get('/my_news', async function (req, res) { bundle, template: 'notifications' }); - notifications.filter(noti => noti.read === false).forEach(function (notification) { - notification.markRead(); - }); }); notificationRouter.get('/friend_requests', async function (req, res) { - let requests = (await getUserFriendRequestsIncoming(req.pid)).reverse(); + const { query, auth } = parseReq(req, { + query: z.object({ + pjax: z.stringbool().optional() + }) + }); + + let requests = (await getUserFriendRequestsIncoming(auth().pid)).reverse(); const now = new Date(); requests = requests.filter(request => new Date(Number(request.expires) * 1000) > new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000)); const userMap = getUserHash(); @@ -40,7 +56,7 @@ notificationRouter.get('/friend_requests', async function (req, res) { userMap }; - if (req.query.pjax) { + if (query.pjax) { return res.render(req.directory + '/partials/requests.ejs', { bundle, moment From d8c054768eb2cf8b00b2eaa87d0acca8f4725393 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 20 Jan 2026 20:59:35 +0100 Subject: [PATCH 097/155] fix: cleanup endpoint data --- .../src/services/juxt-web/routes/console/userpage.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js index 0ebe4254..fcecb204 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js @@ -35,9 +35,17 @@ userPageRouter.get('/notifications.json', async function (req, res) { userPageRouter.get('/downloadUserData.json', async function (req, res) { res.set('Content-Type', 'text/json'); res.set('Content-Disposition', `attachment; filename="${req.pid}_user_data.json"`); - const posts = await POST.find({ pid: req.pid }); + let posts = await POST.find({ pid: req.pid }); const userContent = await database.getUserSettings(req.pid); const userSettings = await database.getUserContent(req.pid); + + // Clean non-user data + userSettings.banned_by = null; + posts = posts.map(post => ({ + ...post, + removed_by: null + })); + const doc = { user_content: userContent, user_settings: userSettings, From 3ad81d7004350a24eaa149956603774ebbfaf80e Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 20 Jan 2026 21:06:50 +0100 Subject: [PATCH 098/155] fix: fixed incorrect naming of variables --- .../src/services/juxt-web/routes/console/userpage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js index fcecb204..560f0050 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/userpage.js @@ -35,21 +35,21 @@ userPageRouter.get('/notifications.json', async function (req, res) { userPageRouter.get('/downloadUserData.json', async function (req, res) { res.set('Content-Type', 'text/json'); res.set('Content-Disposition', `attachment; filename="${req.pid}_user_data.json"`); - let posts = await POST.find({ pid: req.pid }); - const userContent = await database.getUserSettings(req.pid); - const userSettings = await database.getUserContent(req.pid); + const rawPosts = await POST.find({ pid: req.pid }); + const userSettings = await database.getUserSettings(req.pid); + const userContent = await database.getUserContent(req.pid); // Clean non-user data userSettings.banned_by = null; - posts = posts.map(post => ({ - ...post, + const postsJson = rawPosts.map(post => ({ + ...post.toJSON(), removed_by: null })); const doc = { user_content: userContent, user_settings: userSettings, - posts: posts + posts: postsJson }; res.send(doc); }); From b793f581b9ecf4a34e0d16304816c7e9b0aa2278 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 21 Jan 2026 12:15:41 +0100 Subject: [PATCH 099/155] feat: port community list views to JSX --- .../juxt-web/routes/console/communities.tsx | 29 +++++-- .../juxt-web/views/ctr/communityListView.tsx | 85 +++++++++++++++++++ .../views/portal/communityListView.tsx | 82 ++++++++++++++++++ .../juxt-web/views/web/communityListView.tsx | 85 +++++++++++++++++++ 4 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityListView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityListView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/communityListView.tsx diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx index 0ae04743..3db04080 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx @@ -9,7 +9,12 @@ import { POST } from '@/models/post'; import { redisGet, redisRemove, redisSet } from '@/redisCache'; import { getCommunityHash } from '@/util'; import { parseReq } from '@/services/juxt-web/routes/routeUtils'; +import { WebCommunityListView, WebCommunityOverviewView } from '@/services/juxt-web/views/web/communityListView'; +import { buildContext } from '@/services/juxt-web/views/context'; +import { PortalCommunityListView, PortalCommunityOverviewView } from '@/services/juxt-web/views/portal/communityListView'; +import { CtrCommunityListView, CtrCommunityOverviewView } from '@/services/juxt-web/views/ctr/communityListView'; import type { InferSchemaType } from 'mongoose'; +import type { CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView'; import type { CommunitySchema } from '@/models/communities'; const upload = multer({ dest: 'uploads/' }); export const communitiesRouter = express.Router(); @@ -17,17 +22,29 @@ export const communitiesRouter = express.Router(); communitiesRouter.get('/', async function (req, res) { const communityStats = await getCommunityStats(); - res.render(req.directory + '/communities.ejs', { - cache: true, - popularCommunities: communityStats.popular, - newCommunities: communityStats.new + const props: CommunityOverviewViewProps = { + ctx: buildContext(res), + newCommunities: communityStats.new, + popularCommunities: communityStats.popular + }; + res.jsxForDirectory({ + web: , + portal: , + ctr: }); }); communitiesRouter.get('/all', async function (req, res) { const communities = await database.getCommunities(90); - res.render(req.directory + '/all_communities.ejs', { - communities: communities + + const props: CommunityListViewProps = { + ctx: buildContext(res), + communities + }; + res.jsxForDirectory({ + web: , + portal: , + ctr: }); }); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityListView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityListView.tsx new file mode 100644 index 00000000..7bbb887a --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityListView.tsx @@ -0,0 +1,85 @@ +import { utils } from '@/services/juxt-web/views/utils'; +import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root'; +import type { ReactNode } from 'react'; +import type { CommunityItemProps, CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView'; + +function CtrCommunityItem(props: CommunityItemProps): ReactNode { + const id = props.community.olive_community_id; + return ( +
  • + + +
    +
    + {props.community.name} +
    + + {props.community.followers} + {' '} + {props.ctx.lang.community.followers} + +
    +
    +
    +
  • + ); +} + +export function CtrCommunityListView(props: CommunityListViewProps): ReactNode { + return ( + + + +
    +
    +
      + {props.communities.map(community => ( + + ))} +
    +
    +
    +
    +
    + ); +} + +export function CtrCommunityOverviewView(props: CommunityOverviewViewProps): ReactNode { + return ( + + + +
    +
    +
    +

    {props.ctx.lang.all_communities.popular_places}

    +
    +
      + {props.popularCommunities.map(community => ( + + ))} +
    +
    +

    {props.ctx.lang.all_communities.new_communities}

    +
    +
      + {props.newCommunities.map(community => ( + + ))} +
    +
    +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityListView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityListView.tsx new file mode 100644 index 00000000..ce2302ce --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityListView.tsx @@ -0,0 +1,82 @@ +import { utils } from '@/services/juxt-web/views/utils'; +import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root'; +import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar'; +import type { ReactNode } from 'react'; +import type { CommunityItemProps, CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView'; + +function PortalCommunityItem(props: CommunityItemProps): ReactNode { + const id = props.community.olive_community_id; + return ( +
  • + + +
    +
    + {props.community.name} + + {props.community.followers} + {' '} + {props.ctx.lang.community.followers} + +
    +
    +
  • + ); +} + +export function PortalCommunityListView(props: CommunityListViewProps): ReactNode { + return ( + + + + +
    +
    +
      + {props.communities.map(community => ( + + ))} +
    +
    +
    +
    +
    + ); +} + +export function PortalCommunityOverviewView(props: CommunityOverviewViewProps): ReactNode { + return ( + + + + +
    +
    +
    +

    {props.ctx.lang.all_communities.popular_places}

    +
    +
      + {props.popularCommunities.map(community => ( + + ))} +
    +
    +

    {props.ctx.lang.all_communities.new_communities}

    +
    +
      + {props.newCommunities.map(community => ( + + ))} +
    +
    +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityListView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityListView.tsx new file mode 100644 index 00000000..9b7b040f --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityListView.tsx @@ -0,0 +1,85 @@ +import { WebRoot, WebWrapper } from '@/services/juxt-web/views/web/root'; +import { WebNavBar } from '@/services/juxt-web/views/web/navbar'; +import { utils } from '@/services/juxt-web/views/utils'; +import type { ReactNode } from 'react'; +import type { InferSchemaType } from 'mongoose'; +import type { RenderContext } from '@/services/juxt-web/views/context'; +import type { CommunitySchema } from '@/models/communities'; + +export type CommunityListViewProps = { + ctx: RenderContext; + communities: InferSchemaType[]; +}; + +export type CommunityOverviewViewProps = { + ctx: RenderContext; + popularCommunities: InferSchemaType[]; + newCommunities: InferSchemaType[]; +}; + +export type CommunityItemProps = { + ctx: RenderContext; + community: InferSchemaType; +}; + +function WebCommunityItem(props: CommunityItemProps): ReactNode { + return ( + + +

    {props.community.name}

    +

    + {props.community.followers} + {' '} + followers +

    +
    + ); +} + +export function WebCommunityListView(props: CommunityListViewProps): ReactNode { + return ( + +

    + {props.ctx.lang.all_communities.text} +

    + +
    + + + +
    + ); +} + +export function WebCommunityOverviewView(props: CommunityOverviewViewProps): ReactNode { + return ( + +

    + {props.ctx.lang.global.communities} +

    + +
    + +

    {props.ctx.lang.all_communities.popular_places}

    + +

    {props.ctx.lang.all_communities.new_communities}

    +
    + {props.newCommunities.map(community => ( + + ))} +
    +
    + + + +
    + ); +} From cb204e5c82e59762dd77b00647fd51c2271f0ae5 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 21 Jan 2026 12:21:47 +0100 Subject: [PATCH 100/155] chore: remove unused ejs files --- .../webfiles/ctr/all_communities.ejs | 31 ----- .../webfiles/ctr/communities.ejs | 59 --------- apps/juxtaposition-ui/webfiles/ctr/feed.ejs | 28 ----- .../webfiles/ctr/guest_notice.ejs | 91 -------------- .../webfiles/portal/all_communities.ejs | 32 ----- .../webfiles/portal/communities.ejs | 54 --------- .../juxtaposition-ui/webfiles/portal/feed.ejs | 34 ------ .../webfiles/portal/guest_notice.ejs | 113 ------------------ .../webfiles/portal/partials/not_ready.ejs | 1 - .../webfiles/portal/partials/updates.ejs | 26 ---- .../webfiles/portal/scroll_wrapper.ejs | 9 -- .../webfiles/web/all_communities.ejs | 23 ---- .../webfiles/web/communities.ejs | 37 ------ apps/juxtaposition-ui/webfiles/web/feed.ejs | 20 ---- 14 files changed, 558 deletions(-) delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/all_communities.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/communities.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/feed.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/guest_notice.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/all_communities.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/communities.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/feed.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/guest_notice.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/partials/not_ready.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/partials/updates.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/scroll_wrapper.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/web/all_communities.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/web/communities.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/web/feed.ejs diff --git a/apps/juxtaposition-ui/webfiles/ctr/all_communities.ejs b/apps/juxtaposition-ui/webfiles/ctr/all_communities.ejs deleted file mode 100644 index fae78079..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/all_communities.ejs +++ /dev/null @@ -1,31 +0,0 @@ - - -<%- include('partials/head', { title: lang.all_communities.text }); %> - -
    - -
    -
    - -
    -
    - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/ctr/communities.ejs b/apps/juxtaposition-ui/webfiles/ctr/communities.ejs deleted file mode 100644 index aedb78a8..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/communities.ejs +++ /dev/null @@ -1,59 +0,0 @@ - - -<%- include('partials/head', { title: lang.global.communities }); %> - -
    - -
    -
    -
    -

    <%= lang.all_communities.popular_places %>

    -
    - -
    -

    <%= lang.all_communities.new_communities %>

    -
    - -
    -
    -
    - - - diff --git a/apps/juxtaposition-ui/webfiles/ctr/feed.ejs b/apps/juxtaposition-ui/webfiles/ctr/feed.ejs deleted file mode 100644 index 585399ca..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/feed.ejs +++ /dev/null @@ -1,28 +0,0 @@ - - -<%- include('partials/head', { title: title }); %> - -
    - -
    - -
  • - - My Feed - -
  • -
  • - - Global Feed - -
  • -
    -
    - <%- include('partials/' + template, { bundle }); %> -
    -
    -
    - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/ctr/guest_notice.ejs b/apps/juxtaposition-ui/webfiles/ctr/guest_notice.ejs deleted file mode 100644 index 60656750..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/guest_notice.ejs +++ /dev/null @@ -1,91 +0,0 @@ - - - - <% if (typeof uaIsConsole !== 'undefined' && !uaIsConsole) { %> - - - <% } %> - - - - - -
    -

    Welcome to Juxtaposition!

    -
    -

    - Juxt is a gaming community that connects people from all over the world using Mii characters. - Use Juxt to share your gaming experiences and meet people from around the world. -

    -
    - - -
    - - - - - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/all_communities.ejs b/apps/juxtaposition-ui/webfiles/portal/all_communities.ejs deleted file mode 100644 index 2d24055a..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/all_communities.ejs +++ /dev/null @@ -1,32 +0,0 @@ - - -<%- include('partials/head', { title: lang.all_communities.text }); %> - -<%- include('partials/nav_bar', { selection: 2, pid: pid }); %> -
    - -
    -
    -
      - <% for(var i = 0; i < communities.length; i++) {%> - <% var communityID = communities[i].olive_community_id; %> -
    • - - -
      -
      - <%= communities[i].name %> - <%= communities[i].followers %> <%= lang.community.followers %> -
      -
      -
    • - <% } %> -
    -
    -
    -
    - - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/communities.ejs b/apps/juxtaposition-ui/webfiles/portal/communities.ejs deleted file mode 100644 index fd49b4bf..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/communities.ejs +++ /dev/null @@ -1,54 +0,0 @@ - - -<%- include('partials/head', { title: lang.global.communities }); %> - -<%- include('partials/nav_bar', { selection: 2, pid: pid }); %> -
    - -
    -
    -
    -

    <%= lang.all_communities.popular_places %>

    -
    -
      - <% for(var i = 0; i < popularCommunities.length; i++) {%> - <% var popCommunityID = popularCommunities[i].olive_community_id; %> -
    • - - -
      -
      - <%= popularCommunities[i].name %> - <%= popularCommunities[i].followers %> <%= lang.community.followers %> -
      -
      -
    • - <% } %> -
    -
    -

    <%= lang.all_communities.new_communities %>

    -
    -
      - <% for(var j = 0; j < newCommunities.length; j++) {%> - <% var communityID = newCommunities[j].olive_community_id; %> -
    • - - -
      -
      - <%= newCommunities[j].name %> - <%= newCommunities[j].followers %> <%= lang.community.followers %> -
      -
      -
    • - <% } %> -
    -
    -
    -
    - - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/feed.ejs b/apps/juxtaposition-ui/webfiles/portal/feed.ejs deleted file mode 100644 index 0216c912..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/feed.ejs +++ /dev/null @@ -1,34 +0,0 @@ - - -<%- include('partials/head', { title: title }); %> - -<%- include('partials/nav_bar', { selection: -1, pid: pid }); %> -
    - -
    - <%= lang.global.more %> -
    -
    -
    - -
  • - - My Feed - -
  • -
  • - - Global Feed - -
  • -
    -
    - <%- include('partials/' + template, { bundle }); %> -
    -
    -
    - - - diff --git a/apps/juxtaposition-ui/webfiles/portal/guest_notice.ejs b/apps/juxtaposition-ui/webfiles/portal/guest_notice.ejs deleted file mode 100644 index 04512050..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/guest_notice.ejs +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - -
    -

    <%= lang.setup.welcome %>

    -
    -

    - <%= lang.setup.welcome_text %> -

    -
    - - -
    - -
    -

    <%= lang.setup.beta %>

    -
    -

    - <%= lang.setup.beta_text.first %> -
    - <%= lang.setup.beta_text.second %> -
    - <%= lang.setup.beta_text.third %> -

    -
    - - -
    -
    - -
    -

    <%= lang.setup.google %>

    -
    -

    - <%= lang.setup.google_text %> -

    -
    - - -
    - -
    -

    <%= lang.setup.guest %>

    -
    -

    - <%= lang.setup.guest_text %> -

    -
    - -
    - - - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/partials/not_ready.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/not_ready.ejs deleted file mode 100644 index 246e675e..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/partials/not_ready.ejs +++ /dev/null @@ -1 +0,0 @@ -

    This feature is not ready yet

    \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/partials/updates.ejs b/apps/juxtaposition-ui/webfiles/portal/partials/updates.ejs deleted file mode 100644 index 7358c02c..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/partials/updates.ejs +++ /dev/null @@ -1,26 +0,0 @@ -
    -
      -
    • - - - -
      -

      Shutterbug gave - your Comment(Testing...) a Yeah. - 2 minutes ago -

      -
      -
    • -
    • - - - -
      -

      Followed by ♪☆Peyton☆♪, Rocman X, - and Whissu2. 3 minutes ago -

      -
      -
    • -
    -
    \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/scroll_wrapper.ejs b/apps/juxtaposition-ui/webfiles/portal/scroll_wrapper.ejs deleted file mode 100644 index 0bfafb3b..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/scroll_wrapper.ejs +++ /dev/null @@ -1,9 +0,0 @@ -
    -
    -

    - <%= text %> - Go back up - -

    -
    -
    \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/web/all_communities.ejs b/apps/juxtaposition-ui/webfiles/web/all_communities.ejs deleted file mode 100644 index 073581f6..00000000 --- a/apps/juxtaposition-ui/webfiles/web/all_communities.ejs +++ /dev/null @@ -1,23 +0,0 @@ - - - <%- include('partials/head'); %> - - -
    - - <%- include('partials/nav_bar', { selection: 2, pid: pid }); %> -
    -
    - -
    -
    - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/web/communities.ejs b/apps/juxtaposition-ui/webfiles/web/communities.ejs deleted file mode 100644 index e06bbc47..00000000 --- a/apps/juxtaposition-ui/webfiles/web/communities.ejs +++ /dev/null @@ -1,37 +0,0 @@ - - - <%- include('partials/head'); %> - - -
    - - <%- include('partials/nav_bar', { selection: 2, pid: pid }); %> -
    -
    -

    <%= lang.all_communities.popular_places %>

    - -

    <%= lang.all_communities.new_communities %>

    -
    - <% for(var j = 0; j < newCommunities.length; j++) {%> - - -

    <%= newCommunities[j].name %>

    -

    <%= newCommunities[j].followers %> followers

    -
    - <% } %> -
    -
    -
    - -
    -
    - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/web/feed.ejs b/apps/juxtaposition-ui/webfiles/web/feed.ejs deleted file mode 100644 index e9f2f5be..00000000 --- a/apps/juxtaposition-ui/webfiles/web/feed.ejs +++ /dev/null @@ -1,20 +0,0 @@ - - - <%- include('partials/head'); %> - - -
    - - <%- include('partials/nav_bar', { selection: 1, pid: pid }); %> -
    -
    - - <%- include('partials/' + template, { bundle }); %> -
    - <%- include('partials/report_modal', { pid }) %> -
    - - From 578233f8c79a32d22434985650beed285bf2ec75 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 21 Jan 2026 12:32:58 +0100 Subject: [PATCH 101/155] feat: port sub_communities.ejs to JSX --- .../juxt-web/routes/console/communities.tsx | 15 ++++--- .../juxt-web/views/ctr/communityListView.tsx | 2 +- .../juxt-web/views/ctr/subCommunityView.tsx | 29 ++++++++++++++ .../views/portal/communityListView.tsx | 5 ++- .../views/portal/subCommunityView.tsx | 39 +++++++++++++++++++ .../webfiles/ctr/sub_communities.ejs | 31 --------------- .../webfiles/portal/sub_communities.ejs | 32 --------------- 7 files changed, 82 insertions(+), 71 deletions(-) create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/ctr/subCommunityView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/portal/subCommunityView.tsx delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/sub_communities.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/sub_communities.ejs diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx index 3db04080..4251e2ae 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx @@ -13,7 +13,10 @@ import { WebCommunityListView, WebCommunityOverviewView } from '@/services/juxt- import { buildContext } from '@/services/juxt-web/views/context'; import { PortalCommunityListView, PortalCommunityOverviewView } from '@/services/juxt-web/views/portal/communityListView'; import { CtrCommunityListView, CtrCommunityOverviewView } from '@/services/juxt-web/views/ctr/communityListView'; +import { PortalSubCommunityView } from '@/services/juxt-web/views/portal/subCommunityView'; +import { CtrSubCommunityView } from '@/services/juxt-web/views/ctr/subCommunityView'; import type { InferSchemaType } from 'mongoose'; +import type { SubCommunityViewProps } from '@/services/juxt-web/views/portal/subCommunityView'; import type { CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView'; import type { CommunitySchema } from '@/models/communities'; const upload = multer({ dest: 'uploads/' }); @@ -87,17 +90,19 @@ communitiesRouter.get('/:communityID/related', async function (req, res) { message: 'Community not Found' }); } - const communityMap = getCommunityHash(); const children = await database.getSubCommunities(community.olive_community_id); if (!children) { return res.redirect(`/titles/${community.olive_community_id}/new`); } - res.render(req.directory + '/sub_communities.ejs', { - selection: 2, - communityMap, + const props: SubCommunityViewProps = { + ctx: buildContext(res), community, - children + subcommunities: children + }; + res.jsxForDirectory({ + portal: , + ctr: }); }); diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityListView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityListView.tsx index 7bbb887a..63990f0f 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityListView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityListView.tsx @@ -3,7 +3,7 @@ import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root'; import type { ReactNode } from 'react'; import type { CommunityItemProps, CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView'; -function CtrCommunityItem(props: CommunityItemProps): ReactNode { +export function CtrCommunityItem(props: CommunityItemProps): ReactNode { const id = props.community.olive_community_id; return (
  • diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/subCommunityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/subCommunityView.tsx new file mode 100644 index 00000000..d1841517 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/subCommunityView.tsx @@ -0,0 +1,29 @@ +import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root'; +import { CtrCommunityItem } from '@/services/juxt-web/views/ctr/communityListView'; +import type { ReactNode } from 'react'; +import type { SubCommunityViewProps } from '@/services/juxt-web/views/portal/subCommunityView'; + +export function CtrSubCommunityView(props: SubCommunityViewProps): ReactNode { + return ( + + + +
    +
    +
      + {props.subcommunities.map(community => ( + + ))} +
    +
    +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityListView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityListView.tsx index ce2302ce..ce4072e2 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityListView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityListView.tsx @@ -4,11 +4,12 @@ import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar'; import type { ReactNode } from 'react'; import type { CommunityItemProps, CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView'; -function PortalCommunityItem(props: CommunityItemProps): ReactNode { +export function PortalCommunityItem(props: CommunityItemProps): ReactNode { const id = props.community.olive_community_id; + const imageCommunityId = props.community.parent ? props.community.parent : id; return (
  • - +
    diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/subCommunityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/subCommunityView.tsx new file mode 100644 index 00000000..c7b48517 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/subCommunityView.tsx @@ -0,0 +1,39 @@ +import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root'; +import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar'; +import { PortalCommunityItem } from '@/services/juxt-web/views/portal/communityListView'; +import type { ReactNode } from 'react'; +import type { InferSchemaType } from 'mongoose'; +import type { RenderContext } from '@/services/juxt-web/views/context'; +import type { CommunitySchema } from '@/models/communities'; + +export type SubCommunityViewProps = { + ctx: RenderContext; + community: InferSchemaType; + subcommunities: InferSchemaType[]; +}; + +export function PortalSubCommunityView(props: SubCommunityViewProps): ReactNode { + return ( + + + + +
    +
    +
      + {props.subcommunities.map(community => ( + + ))} +
    +
    +
    +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/webfiles/ctr/sub_communities.ejs b/apps/juxtaposition-ui/webfiles/ctr/sub_communities.ejs deleted file mode 100644 index 63159f0a..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/sub_communities.ejs +++ /dev/null @@ -1,31 +0,0 @@ - - -<%- include('partials/head', { title: lang.all_communities.text }); %> - -
    - -
    -
    - -
    -
    - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/sub_communities.ejs b/apps/juxtaposition-ui/webfiles/portal/sub_communities.ejs deleted file mode 100644 index 8c6383bb..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/sub_communities.ejs +++ /dev/null @@ -1,32 +0,0 @@ - - -<%- include('partials/head', { title: lang.global.communities }); %> - -<%- include('partials/nav_bar', { selection: 2, pid: pid }); %> -
    - -
    -
    -
      - <% for(var i = 0; i < children.length; i++) {%> - <% var communityID = children[i].olive_community_id; %> -
    • - - -
      -
      - <%= children[i].name %> - <%= children[i].followers %> <%= lang.community.followers %> -
      -
      -
    • - <% } %> -
    -
    -
    -
    - - - \ No newline at end of file From f755c610dc28391a9db189d2b31f2b8415b9158b Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 21 Jan 2026 13:34:30 +0100 Subject: [PATCH 102/155] feat: port community view to JSX --- .../juxt-web/routes/console/communities.tsx | 101 ++++---- .../juxt-web/views/ctr/communityView.tsx | 115 ++++++++++ .../juxt-web/views/portal/communityView.tsx | 216 ++++++++++++++++++ .../juxt-web/views/web/communityView.tsx | 140 ++++++++++++ .../webfiles/ctr/community.ejs | 68 ------ .../webfiles/portal/community.ejs | 104 --------- .../webfiles/web/community.ejs | 73 ------ 7 files changed, 526 insertions(+), 291 deletions(-) create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityView.tsx create mode 100644 apps/juxtaposition-ui/src/services/juxt-web/views/web/communityView.tsx delete mode 100644 apps/juxtaposition-ui/webfiles/ctr/community.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/portal/community.ejs delete mode 100644 apps/juxtaposition-ui/webfiles/web/community.ejs diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx index 4251e2ae..8d8ff562 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx @@ -1,5 +1,4 @@ import express from 'express'; -import moment from 'moment'; import multer from 'multer'; import { z } from 'zod'; import { config } from '@/config'; @@ -7,7 +6,6 @@ import { database } from '@/database'; import { COMMUNITY } from '@/models/communities'; import { POST } from '@/models/post'; import { redisGet, redisRemove, redisSet } from '@/redisCache'; -import { getCommunityHash } from '@/util'; import { parseReq } from '@/services/juxt-web/routes/routeUtils'; import { WebCommunityListView, WebCommunityOverviewView } from '@/services/juxt-web/views/web/communityListView'; import { buildContext } from '@/services/juxt-web/views/context'; @@ -15,7 +13,15 @@ import { PortalCommunityListView, PortalCommunityOverviewView } from '@/services import { CtrCommunityListView, CtrCommunityOverviewView } from '@/services/juxt-web/views/ctr/communityListView'; import { PortalSubCommunityView } from '@/services/juxt-web/views/portal/subCommunityView'; import { CtrSubCommunityView } from '@/services/juxt-web/views/ctr/subCommunityView'; +import { WebCommunityView } from '@/services/juxt-web/views/web/communityView'; +import { PortalCommunityView } from '@/services/juxt-web/views/portal/communityView'; +import { CtrCommunityView } from '@/services/juxt-web/views/ctr/communityView'; +import { WebPostListView } from '@/services/juxt-web/views/web/postList'; +import { PortalPostListView } from '@/services/juxt-web/views/portal/postList'; +import { CtrPostListView } from '@/services/juxt-web/views/ctr/postList'; import type { InferSchemaType } from 'mongoose'; +import type { PostListViewProps } from '@/services/juxt-web/views/web/postList'; +import type { CommunityViewProps } from '@/services/juxt-web/views/web/communityView'; import type { SubCommunityViewProps } from '@/services/juxt-web/views/portal/subCommunityView'; import type { CommunityListViewProps, CommunityOverviewViewProps } from '@/services/juxt-web/views/web/communityListView'; import type { CommunitySchema } from '@/models/communities'; @@ -139,11 +145,7 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { }; await community.save(); } - const communityMap = getCommunityHash(); - let children: InferSchemaType[] | null = await database.getSubCommunities(community.olive_community_id); - if (children !== null && children.length === 0) { - children = null; - } + const subCommunities = await database.getSubCommunities(community.olive_community_id); let posts; let type; @@ -159,36 +161,47 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { } const numPosts = await database.getTotalPostsByCommunity(community); - const bundle = { + const postListProps: PostListViewProps = { + ctx: buildContext(res), + nextLink: `/titles/${params.communityID}/${params.type}/more?offset=${posts.length}&pjax=true`, posts, - open: community.permissions.open, - numPosts, - communityMap, - userContent, - link: `/titles/${params.communityID}/${params.type}/more?offset=${posts.length}&pjax=true` + userContent }; if (query.pjax) { - return res.render(req.directory + '/partials/posts_list.ejs', { - bundle, - moment + return res.jsxForDirectory({ + web: , + portal: , + ctr: }); } - res.render(req.directory + '/community.ejs', { - // EJS variable and server-side variable - moment: moment, - community: community, - communityMap: communityMap, - posts: posts, - totalNumPosts: numPosts, - userSettings: userSettings, - userContent: userContent, + const props: CommunityViewProps = { + ctx: buildContext(res), + feedType: type, pnid: auth().user, - children, - type, - bundle, - template: 'posts_list' + community, + hasSubCommunities: subCommunities.length > 0, + totalPosts: numPosts, + userContent, + userSettings + }; + res.jsxForDirectory({ + web: ( + + + + ), + portal: ( + + + + ), + ctr: ( + + + + ) }); }); @@ -205,10 +218,9 @@ communitiesRouter.get('/:communityID/:type/more', async function (req, res) { const offset = query.offset; const userContent = await database.getUserContent(auth().pid); - const communityMap = getCommunityHash(); let posts; const community = await database.getCommunityByID(params.communityID); - if (!community) { + if (!community || !userContent) { return res.redirect('/404'); } switch (params.type) { @@ -223,25 +235,22 @@ communitiesRouter.get('/:communityID/:type/more', async function (req, res) { break; } - const bundle = { + const postListProps: PostListViewProps = { + ctx: buildContext(res), + nextLink: `/titles/${params.communityID}/${params.type}/more?offset=${offset + posts.length}&pjax=true`, posts, - open: true, - numPosts: posts.length, - communityMap, - userContent, - link: `/titles/${params.communityID}/${params.type}/more?offset=${offset + posts.length}&pjax=true` + userContent }; - if (posts.length > 0) { - res.render(req.directory + '/partials/posts_list.ejs', { - communityMap: communityMap, - moment: moment, - database: database, - bundle - }); - } else { - res.sendStatus(204); + if (posts.length === 0) { + return res.sendStatus(204); } + + return res.jsxForDirectory({ + web: , + portal: , + ctr: + }); }); communitiesRouter.post('/follow', upload.none(), async function (req, res) { diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityView.tsx new file mode 100644 index 00000000..2e94297c --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityView.tsx @@ -0,0 +1,115 @@ +import cx from 'classnames'; +import { utils } from '@/services/juxt-web/views/utils'; +import { CtrPageBody, CtrRoot } from '@/services/juxt-web/views/ctr/root'; +import { CtrNewPostView } from '@/services/juxt-web/views/ctr/newPostView'; +import type { ReactNode } from 'react'; +import type { CommunityViewProps } from '@/services/juxt-web/views/web/communityView'; + +export function CtrCommunityView(props: CommunityViewProps): ReactNode { + const community = props.community; + const imageId = community.parent ? community.parent : community.olive_community_id; + const bannerUrl = community.ctr_header + ? utils.cdn(props.ctx, community.ctr_header) + : utils.cdn(props.ctx, `/headers/${imageId}/3DS.png`); + const isUserFollowing = props.userContent.followed_communities.includes(community.olive_community_id); + const canPost = (( + (community.permissions.open && community.type < 2) || (community.admins && community.admins.indexOf(props.ctx.pid) !== -1) || + (props.pnid.accessLevel >= community.permissions.minimum_new_post_access_level) + ) && props.userSettings.account_status === 0); + + return ( + + + +
    +
    +
    + +
  • + {props.ctx.lang.community.recent} +
  • +
  • + {props.ctx.lang.community.popular} +
  • +
    +
    + {props.children} +
    +
    + +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityView.tsx new file mode 100644 index 00000000..879db348 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/portal/communityView.tsx @@ -0,0 +1,216 @@ +import cx from 'classnames'; +import { utils } from '@/services/juxt-web/views/utils'; +import { PortalPageBody, PortalRoot } from '@/services/juxt-web/views/portal/root'; +import { PortalNavBar } from '@/services/juxt-web/views/portal/navbar'; +import { PortalNewPostView } from '@/services/juxt-web/views/portal/newPostView'; +import type { ReactNode } from 'react'; +import type { CommunityViewProps } from '@/services/juxt-web/views/web/communityView'; + +export function PortalCommunityView(props: CommunityViewProps): ReactNode { + const community = props.community; + const imageId = community.parent ? community.parent : community.olive_community_id; + const bannerUrl = community.wup_header + ? utils.cdn(props.ctx, community.wup_header) + : utils.cdn(props.ctx, `/headers/${imageId}/WiiU.png`); + const isUserFollowing = props.userContent.followed_communities.includes(community.olive_community_id); + const canPost = (( + (community.permissions.open && community.type < 2) || (community.admins && community.admins.indexOf(props.ctx.pid) !== -1) || + (props.pnid.accessLevel >= community.permissions.minimum_new_post_access_level) + ) && props.userSettings.account_status === 0); + + return ( + + + + +
    +
    + +
    + + + +
  • + + + {props.ctx.lang.community.recent} + + +
  • +
  • + + + {props.ctx.lang.community.popular} + + +
  • +
    +
    + + {props.ctx.lang.global.more} + +
    +
    +
    + {props.children} +
    +
    + +
    +
    + ); +} diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityView.tsx new file mode 100644 index 00000000..c0249d28 --- /dev/null +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/web/communityView.tsx @@ -0,0 +1,140 @@ +import cx from 'classnames'; +import { WebRoot, WebWrapper } from '@/services/juxt-web/views/web/root'; +import { WebNavBar } from '@/services/juxt-web/views/web/navbar'; +import { utils } from '@/services/juxt-web/views/utils'; +import { WebReportModalView } from '@/services/juxt-web/views/web/reportModalView'; +import type { ReactNode } from 'react'; +import type { InferSchemaType } from 'mongoose'; +import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { RenderContext } from '@/services/juxt-web/views/context'; +import type { CommunitySchema } from '@/models/communities'; +import type { HydratedSettingsDocument } from '@/models/settings'; +import type { ContentSchema } from '@/models/content'; + +export type CommunityViewProps = { + ctx: RenderContext; + community: InferSchemaType; + totalPosts: number; + userSettings: HydratedSettingsDocument; + userContent: InferSchemaType; + pnid: GetUserDataResponse; + hasSubCommunities: boolean; + feedType: number; + children?: ReactNode; +}; + +export function WebCommunityHead(props: CommunityViewProps): ReactNode { + const name = props.community.name; + const title = `Juxt - ${name}`; + const description = props.community.description; + const image = utils.cdn(props.ctx, `/icons/${props.community.olive_community_id}/128.png`); + const url = `https://juxt.pretendo.cc/communities/${props.community.olive_community_id}/new`; + + return ( + <> + {title} + {/* Google / Search Engine Tags */} + + + + + {/* Open Graph Meta Tags */} + + + + + + + {/* Twitter Meta Tags */} + + + + + + + + ); +} + +export function WebCommunityView(props: CommunityViewProps): ReactNode { + const community = props.community; + const imageId = community.parent ? community.parent : community.olive_community_id; + const bannerUrl = community.wup_header + ? utils.cdn(props.ctx, community.wup_header) + : utils.cdn(props.ctx, `/headers/${imageId}/WiiU.png`); + const isUserFollowing = props.userContent.followed_communities.includes(community.olive_community_id); + + return ( + }> +

    + {props.ctx.lang.global.communities} +

    + +
    + +
    + +
    + +

    {community.name}

    + {community.permissions.open + ? ( + + {isUserFollowing ? props.ctx.lang.user_page.following_user : props.ctx.lang.user_page.follow_user} + + ) + : null} +
    +

    {community.description}

    + +
    +
    +

    {props.ctx.lang.community.followers}

    +

    {community.followers}

    +
    +
    +

    {props.ctx.lang.community.posts}

    +

    {props.totalPosts}

    +
    +
    +

    {props.ctx.lang.community.tags}

    +

    N/A

    +
    +
    +
    + + {props.children} +
    + +
    + ); +} diff --git a/apps/juxtaposition-ui/webfiles/ctr/community.ejs b/apps/juxtaposition-ui/webfiles/ctr/community.ejs deleted file mode 100644 index eb9156d8..00000000 --- a/apps/juxtaposition-ui/webfiles/ctr/community.ejs +++ /dev/null @@ -1,68 +0,0 @@ - - -<%- include('partials/head', { title: community.name }); %> - -
    - -
    -
    -
    - -
  • - <%= lang.community.recent %> -
  • -
  • <%= lang.community.popular %>
  • -
    -
    - <%- include('partials/' + template, { bundle }); %> -
    -
    - <%- include('partials/new_post', { pid, lang, id: community.olive_community_id, name: community.name, url: '/posts/new', show: 'community-post-list', message_pid: '' }); %> -
    - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/portal/community.ejs b/apps/juxtaposition-ui/webfiles/portal/community.ejs deleted file mode 100644 index a98e7ca8..00000000 --- a/apps/juxtaposition-ui/webfiles/portal/community.ejs +++ /dev/null @@ -1,104 +0,0 @@ - - -<%- include('partials/head', { title: community.name }); %> - - <%- include('partials/nav_bar', { selection: 2, pid: pid }); %> -
    - -
    -
    - <% if (community.wup_header) { %> - - <% } else { %> - - <% } %> -
    - -
    - - <%if(community.permissions.open){%> - - <%}%> - <%= community.name %> - - - - - - - - <%= bundle.numPosts %>| - - - - - - - - - - <%= community.followers %> - - - -
    - -
  • - - <%= lang.community.recent %> - -
  • -
  • - <%= lang.community.popular %> -
  • -
    -
    - - <%= lang.global.more %> - -
    -
    -
    - <%- include('partials/' + template, { bundle }); %> -
    -
    - <%- include('partials/new_post', { pid, lang, id: community.olive_community_id, name: community.name, - url: '/posts/new' , show: 'community-post-list' , message_pid: '' }); %> -
    - - - \ No newline at end of file diff --git a/apps/juxtaposition-ui/webfiles/web/community.ejs b/apps/juxtaposition-ui/webfiles/web/community.ejs deleted file mode 100644 index 03fd7bfb..00000000 --- a/apps/juxtaposition-ui/webfiles/web/community.ejs +++ /dev/null @@ -1,73 +0,0 @@ - - - Juxt - <%= community.name %> - <%- include('partials/head'); %> - - - - - - - - - - - - - - - - - - - - - - -
    - - <%- include('partials/nav_bar', { selection: 2, pid: pid }); %> -
    -
    -
    - <% if (community.wup_header) { %> - - <% } else { %> - - <% } %> -
    - -

    <%= community.name %>

    - <%if(community.permissions.open){%> - <% var following = userContent.followed_communities.indexOf(community.olive_community_id) !== -1; %> - <% if(following){ %> <%= lang.user_page.following_user %> <%} else {%> <%= lang.user_page.follow_user %> <%}%> - <%}%> -
    -

    <%= community.description %>

    - -
    -
    -

    <%= lang.community.followers %>

    -

    <%= community.followers %>

    -
    -
    -

    <%= lang.community.posts %>

    -

    <%=totalNumPosts%>

    -
    -
    -

    <%= lang.community.tags %>

    -

    N/A

    -
    -
    -
    - - <%- include('partials/' + template, { bundle }); %> -
    - <%- include('partials/report_modal', { pid }) %> -
    - - From 4ac2d41f99d73deee659c453b45c41b1da26c1a0 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 21 Jan 2026 13:39:30 +0100 Subject: [PATCH 103/155] chore: relocate permission logic to the router instead of the template --- .../services/juxt-web/routes/console/communities.tsx | 12 +++++++++--- .../services/juxt-web/views/ctr/communityView.tsx | 9 ++------- .../services/juxt-web/views/portal/communityView.tsx | 9 ++------- .../services/juxt-web/views/web/communityView.tsx | 8 ++------ 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx index 8d8ff562..f817f0d4 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/routes/console/communities.tsx @@ -145,6 +145,13 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { }; await community.save(); } + const canPost = ( + (community.permissions.open && community.type < 2) || + (community.admins && community.admins.indexOf(auth().pid) !== -1) || + (auth().user.accessLevel >= community.permissions.minimum_new_post_access_level) + ) && userSettings.account_status === 0; + const isUserFollowing = userContent.followed_communities.includes(community.olive_community_id); + const subCommunities = await database.getSubCommunities(community.olive_community_id); let posts; let type; @@ -179,12 +186,11 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) { const props: CommunityViewProps = { ctx: buildContext(res), feedType: type, - pnid: auth().user, community, hasSubCommunities: subCommunities.length > 0, totalPosts: numPosts, - userContent, - userSettings + canPost, + isUserFollowing }; res.jsxForDirectory({ web: ( diff --git a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityView.tsx b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityView.tsx index 2e94297c..55b55459 100644 --- a/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityView.tsx +++ b/apps/juxtaposition-ui/src/services/juxt-web/views/ctr/communityView.tsx @@ -11,11 +11,6 @@ export function CtrCommunityView(props: CommunityViewProps): ReactNode { const bannerUrl = community.ctr_header ? utils.cdn(props.ctx, community.ctr_header) : utils.cdn(props.ctx, `/headers/${imageId}/3DS.png`); - const isUserFollowing = props.userContent.followed_communities.includes(community.olive_community_id); - const canPost = (( - (community.permissions.open && community.type < 2) || (community.admins && community.admins.indexOf(props.ctx.pid) !== -1) || - (props.pnid.accessLevel >= community.permissions.minimum_new_post_access_level) - ) && props.userSettings.account_status === 0); return ( @@ -53,7 +48,7 @@ export function CtrCommunityView(props: CommunityViewProps): ReactNode { - {canPost + {props.canPost ? ( = community.permissions.minimum_new_post_access_level) - ) && props.userSettings.account_status === 0); return (
  • + {!community.permissions.open ? : null} {props.children}