Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions apps/juxtaposition-ui/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand All @@ -46,6 +51,6 @@ export default defineConfig([
...globals.builtin
}
},
ignores: ['webfiles/**/*.js']
ignores: ['webfiles/**/*.js', 'webfiles/**/*.ts']
}
]);
10 changes: 8 additions & 2 deletions apps/juxtaposition-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -56,16 +57,21 @@
"zod": "^4.1.13"
},
"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",
"@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",
"@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",
Expand Down
2 changes: 2 additions & 0 deletions apps/juxtaposition-ui/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function CtrMessageThreadView(props: MessageThreadViewProps): ReactNode {
const otherUserName = props.ctx.usersMap.get(props.otherUser.pid) ?? '';

return (
<CtrRoot ctx={props.ctx} title={props.ctx.lang.global.messages}>
<CtrRoot ctx={props.ctx} title={props.ctx.lang.global.messages} onLoad="cave.toolbar_setActiveButton(4);window.scrollTo(0, 500000);">
<CtrPageBody>
<header id="header" className="buttons">
<h1 id="page-title">{otherUserName}</h1>
Expand All @@ -73,7 +73,6 @@ export function CtrMessageThreadView(props: MessageThreadViewProps): ReactNode {
{props.messages.map(msg => <MessageThreadItem key={msg.id} ctx={props.ctx} message={msg} />)}
</div>
<CtrNewPostView ctx={props.ctx} id={props.conversation.id} name={otherUserName} url="/friend_messages/new" show="message-page" messagePid={props.otherUser.pid} />
<img src="" evt-error="cave.toolbar_setActiveButton(4);setTimeout(function() { window.scrollTo(0, 500000); }, 1000)" />
</CtrPageBody>
</CtrRoot>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function CtrPostPageView(props: PostPageViewProps): ReactNode {
: null}
{post.pid === props.ctx.pid
? (
<a id="header-communities-button" className="delete header-button right" href="#" data-post={post.id} evt-click="deletePost(this)">Delete Post</a>
<a id="header-communities-button" className="delete header-button right" href="#" data-button-delete-post={post.id}>Delete Post</a>
)
: (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function PortalMessageThreadView(props: MessageThreadViewProps): ReactNod
const otherUserName = props.ctx.usersMap.get(props.otherUser.pid) ?? '';

return (
<PortalRoot title={props.ctx.lang.global.messages}>
<PortalRoot title={props.ctx.lang.global.messages} onLoad="window.scrollTo(0, 50000);">
<PortalNavBar ctx={props.ctx} selection={3} />
<PortalPageBody>
<header id="header">
Expand All @@ -63,7 +63,6 @@ export function PortalMessageThreadView(props: MessageThreadViewProps): ReactNod
{props.messages.map(msg => <MessageThreadItem key={msg.id} ctx={props.ctx} message={msg} />)}
</div>
<PortalNewPostView ctx={props.ctx} id={props.conversation.id} name={otherUserName} url="/friend_messages/new" show="message-page" messagePid={props.otherUser.pid} />
<img src="" evt-error="setTimeout(function() { window.scrollTo(0, 50000); }, 500)" />
</PortalPageBody>
</PortalRoot>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function PortalPostView(props: PostViewProps): ReactNode {
{ props.isReply && (post.pid === props.ctx.pid || isModerator)
? (
<div>
<button type="button" className="submit remove" data-post={post.id} evt-click="deletePost(this)"></button>
<button type="button" className="submit remove" data-button-delete-post={post.id}></button>
</div>
)
: null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function PortalPostPageView(props: PostPageViewProps): ReactNode {
? (
post.pid === props.ctx.pid
? (
<a id="header-communities-button" className="delete" href="#" data-post={post.id} evt-click="deletePost(this)">Delete Post</a>
<a id="header-communities-button" className="delete" href="#" data-button-delete-post={post.id}>Delete Post</a>
)
: (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ export function WebMessageThreadView(props: MessageThreadViewProps): ReactNode {
<script src="/js/painting.global.js" />
</div>
</div>
<img src="" evt-error="setTimeout(function() { window.scrollTo(0, 50000); }, 500)" />
<WebReportModalView ctx={props.ctx} />
</WebRoot>
);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions apps/juxtaposition-ui/webfiles/ctr/js/api.ts
Original file line number Diff line number Diff line change
@@ -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 {
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, reason: string | null, cb: (result: DeletePostResponse) => void): void {
var postUrl = `/posts/${id}`;
var 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...
var nextUrl = xhr.responseText;
if (!/^\/[\w/]+$/.test(nextUrl)) {
return cb({ status: 400, nextUrl: postUrl });
}

// make a response object
return cb({ status: 200, nextUrl });
});
}
86 changes: 19 additions & 67 deletions apps/juxtaposition-ui/webfiles/ctr/js/juxt.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Pjax } from './pjax';
import { GET, POST } from './xhr';
import { initPostPageView } from './post';
import { empathyPostById } from './api';

var pjax;
setInterval(checkForUpdates, 30000);
Expand Down Expand Up @@ -134,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');
Expand All @@ -152,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);
Expand Down Expand Up @@ -277,33 +278,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');
Expand Down Expand Up @@ -343,6 +317,7 @@ function initAll() {
initMorePosts();
initPostModules();
initTabs();
initPostPageView();
checkForUpdates();
initToolbarConfigs();
pjax.refresh();
Expand Down Expand Up @@ -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]',
Expand Down Expand Up @@ -547,3 +486,16 @@ document.addEventListener('PjaxDone', function (_e) {
cave.requestGc();
cave.transition_end();
});

/* CTR WebKit has an odd bug where images that fail to load throw off the
* entire page render, spritesheets, layout.
* Ensuring we have *something* loaded seems to fix it.
* Ideally we never trigger this since we don't have page errors, but it happens,
* especially when selfhosting. */
document.addEventListener('error', (e) => {
var placeholder = '/images/placeholder.gif';
var target = e.target;
if (target.tagName === 'IMG' && target.getAttribute('src') !== placeholder) {
target.setAttribute('src', placeholder);
}
}, true);
44 changes: 44 additions & 0 deletions apps/juxtaposition-ui/webfiles/ctr/js/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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-button-delete-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) {
return;
}

deletePostById(id, null, function (response) {
if (response.status !== 200) {
return cave.error_callFreeErrorViewer(
5980030,
`Post was not able to be deleted. Please try again later. (${response.status})`
);
}

alert('Post has been deleted.');
window.location.href = response.nextUrl;
});
}

function initDeleteButton(): void {
var del = document.querySelector('[data-button-delete-post]');
if (!del) {
return;
}

(del as HTMLElement).addEventListener('click', deletePost);
}

export function initPostPageView(): void {
initDeleteButton();
}
Loading