Ability to post in Forum

This commit is contained in:
metacryst
2025-11-25 10:17:01 -06:00
parent 7f4a9a8b18
commit 89702efa3a
27 changed files with 494 additions and 254 deletions

View File

@@ -1,6 +1,7 @@
/*
Sam Russell
Captured Sun
11.24.25 - Fixing onClick because it was reversed, adding event to onHover params
11.23.25 - Added onSubmit() event for form submission, added marginHorizontal() and marginVertical()
11.20.25 - Added "pct" style unit, added alignVertical and alignHorizontal for flex boxes
11.19.25 - Allowing for "auto" values in otherwise numeric styles, adding vmin and vmax units
@@ -496,7 +497,6 @@ HTMLElement.prototype.alignVertical = function (value) {
}
if (direction === "column" || direction === "column-reverse") {
console.log("using justify")
this.style.justifyContent = value;
} else {
this.style.alignItems = value;
@@ -824,8 +824,8 @@ HTMLElement.prototype.onAppear = function(func) {
};
HTMLElement.prototype.onClick = function(func) {
const onMouseDown = () => func.call(this, true);
const onMouseUp = () => func.call(this, false);
const onMouseDown = () => func.call(this, false);
const onMouseUp = () => func.call(this, true);
this._storeListener("mousedown", onMouseDown);
this._storeListener("mouseup", onMouseUp);
return this;
@@ -847,8 +847,8 @@ HTMLElement.prototype.onRightClick = function(func) {
};
HTMLElement.prototype.onHover = function(cb) {
const onEnter = () => cb.call(this, true);
const onLeave = () => cb.call(this, false);
const onEnter = (e) => cb.call(this, true, e);
const onLeave = (e) => cb.call(this, false, e);
this._storeListener("mouseover", onEnter);
this._storeListener("mouseleave", onLeave);
return this;

8
ui/_/code/zod.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,5 @@
import './MessagesPanel.js'
css(`
forum- {
font-family: 'Bona';
@@ -23,8 +25,6 @@ css(`
`)
class Forum extends Shadow {
friends = []
conversations = []
render() {
ZStack(() => {
@@ -46,13 +46,25 @@ class Forum extends Shadow {
.gap(0, em)
VStack(() => {
input("Message Hyperia", "80%")
MessagesPanel()
input("Message Hyperia", "93%")
.paddingVertical(1, em)
.paddingHorizontal(2, em)
.color("var(--accent)")
.background("var(--darkbrown)")
.marginBottom(6, em)
.border("none")
.fontSize(1, em)
.onKeyDown(function (e) {
if (e.key === "Enter") {
window.Socket.send({app: "FORUM", operation: "SEND", msg: {forum: "HY", text: this.value }})
this.value = ""
}
})
})
.gap(1, em)
.width(100, pct)
.alignHorizontal("center")
.alignVertical("end")
@@ -120,6 +132,8 @@ class Forum extends Shadow {
.width(100, pct)
.height(100, pct)
}
}
register(Forum)

View File

@@ -0,0 +1,83 @@
class MessagesPanel extends Shadow {
forums = [
"HY"
]
messages = []
render() {
VStack(() => {
if(this.messages.length > 0) {
for(let i=0; i<this.messages.length; i++) {
let message = this.messages[i]
VStack(() => {
HStack(() => {
p(message.sentBy)
.fontWeight("bold")
.marginBottom(0.3, em)
p(this.formatTime(message.time))
.opacity(0.2)
.marginLeft(1, em)
})
p(message.text)
})
}
} else {
div()
.borderRadius(100, pct)
.width(2, em).height(2, em)
.x(45, pct).y(50, pct)
.center()
.backgroundColor("var(--accent")
.transition("transform 1.75s ease-in-out")
.onAppear(function () {
let growing = true;
setInterval(() => {
if (growing) {
this.style.transform = "scale(1.5)";
} else {
this.style.transform = "scale(0.7)";
}
growing = !growing;
}, 750);
});
}
})
.gap(1, em)
.position("relative")
.overflow("scroll")
.height(95, pct)
.width(93, pct)
.paddingTop(2, em)
.paddingBottom(2, em)
.paddingHorizontal(2, em)
.backgroundColor("var(--darkbrown)")
.onAppear(async () => {
console.log("appear")
let res = await Socket.send({app: "FORUM", operation: "GET", msg: {forum: "HY", number: 100}})
if(!res) console.error("failed to get messages")
if(res.msg.length > 0 && this.messages.length === 0) {
console.log("rerendering", res.msg)
this.messages = res.msg
this.rerender()
}
window.addEventListener("new-message", (e) => {
this.messages = e.detail
this.rerender()
})
})
.scrollTop = this.scrollHeight
}
formatTime(str) {
// Extract the time part with am/pm
const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i);
if (!match) return null;
const [_, hourMin, ampm] = match;
return hourMin + ampm.toLowerCase();
}
}
register(MessagesPanel)

View File

@@ -9,14 +9,13 @@ css(`
app-menu.minimized {
color: var(--accent);
transform: translate(-50%, 65%);
border: 1px solid var(--accent);
border: 0.2px solid var(--accent);
padding-top: 0.5em;
padding-left: 2em;
padding-right: 2em;
padding-bottom: 4em;
bottom: 1em;
border-radius: 12px;
background-color: var(--green);
}
app-menu p {

View File

@@ -10,16 +10,45 @@ class ProfileButton extends Shadow {
.borderRadius(5, px)
ProfileMenu()
.x(0, vw).y(4, em)
.center()
})
.display("block")
.onHover((hovering) => {
.onAppear(async () => {
try {
const res = await fetch("/profile", {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json"
}
});
if (!res.ok) throw new Error("Failed to fetch profile");
const profile = await res.json();
console.log(profile);
return profile;
} catch (err) {
console.error(err);
}
})
.onHover((hovering, e) => {
console.log(hovering)
if(hovering) {
console.log(e.target)
if(hovering && !e.target.matches("profile-menu")) {
this.$("img").backgroundColor("var(--accent)")
this.$("img").border("1px solid black")
} else {
this.$("img").style.outline = "1px solid black"
} else if(!e.target.matches("profile-menu")) {
this.$("img").backgroundColor("")
this.$("img").border("")
this.$("img").style.outline = ""
}
})
.onClick((done) => {
console.log(done)
if(done) {
this.$("profile-menu").style.display = ""
}
})

View File

@@ -1,51 +1,11 @@
css(`
profile-menu {
position: absolute;
right: -.5em;
top: -.3em;
width: 0;
height: 0;
background-color: rgba(255, 223, 180, 0.56);
color: black;
transition: width 0.2s, height 0.2s;
border-radius: 12px;
}
profile-menu * {
transition: opacity 0.3s;
}
profile-menu.closed * {
opacity: 0;
}
profile-menu.open {
width: 10em;
height: 15em;
display: block;
}
profile-menu.open * {
opacity: 100%;
}
`)
class ProfileMenu extends Shadow {
render() {
ZStack(() => {
if(this.classList.contains("open")) {
setTimeout(() => {
// this.innerHTML = /* html */`
// <a href="signout" style="position: absolute; left: 0px; color: white; z-index: 2">sign out</a>
// <p>Profile</p>
// `
}, 200)
}
})
util.observeClassChange(this, (cl) => {
p("profile")
})
.backgroundColor("var(--accent)")
.display("none")
}
}

View File

@@ -6,6 +6,7 @@
<link rel="icon" href="/_/icons/logo.svg">
<link rel="stylesheet" href="/_/code/shared.css">
<script src="/_/code/quill.js"></script>
<script src="/_/code/zod.js"></script>
<script type="module" src="75820185/index.js"></script>
</head>
<body style="margin: 0px">

View File

@@ -1,5 +1,5 @@
import ConnectionHandler from "./ws/ConnectionHandler.js"
window.ConnectionHandler = new ConnectionHandler()
import Socket from "./ws/Socket.js"
import "./components/Home.js"
window.Socket = new Socket()
Home()

View File

@@ -4,8 +4,9 @@ class Connection {
linkCreated;
wsStatus;
constructor() {
constructor(receiveCB) {
this.init()
this.receiveCB = receiveCB
}
init() {
@@ -17,41 +18,13 @@ class Connection {
this.ws.addEventListener('open', () => {
this.connectionTries = 0
console.log("Websocket connection established.");
this.ws.addEventListener('message', this.onMessage)
this.ws.addEventListener('message', this.receiveCB)
});
this.ws.addEventListener("close", () => {
this.checkOpen();
console.log('Websocket Closed')
})
}
onMessage = (event) => {
let message = event.data;
let messageSplit = message.split('|');
switch(messageSplit[0]) {
case 'connected':
Forms.connected(messageSplit[1], messageSplit[2])
break;
case 'disconnected':
Forms.disconnected(messageSplit[1])
break;
case 'UPDATE-SIZE':
updateFileSize(messageSplit[1], messageSplit[2]);
break;
case 'URL-TITLE-RESPONSE':
this.updateLinkTitle(messageSplit[1]);
break;
default:
console.log('Websocket message:', message);
break;
}
if (messageSplit[1] == 'CSS-LINK') {
this.createCSSLink(message);
}
}
async checkOpen() {
if (this.ws.readyState === WebSocket.OPEN) {
@@ -70,65 +43,20 @@ class Connection {
});
}
sendMessage = (msg) => {
send = (msg) => {
console.log("sending")
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(msg);
}
else if(this.connectionTries === 0) {
setTimeout(() => {
this.send(msg)
}, 100)
}
else {
console.log('No websocket connection: Cannot send message');
console.error('No websocket connection: Cannot send message');
}
}
updateCSS = async (updateStyle, newValue) => {
let currentSpace = window.wrapper.getAttribute("name").replace(/ /g,'%20')
let currentFolder = window.location.pathname;
//create the inital link
this.linkCreated = false;
let cssIncomingText = ""
Array.from(document.styleSheets).forEach(el => {
if(el?.href?.includes?.(`.${currentSpace}.css`)) {
this.linkCreated = true;
}
})
let currentSpaceDataAttr = window.wrapper.getAttribute("name");
let cssInit = `parchment-page[name="${currentSpaceDataAttr}"] {background-color: #e9c9a0;} parchment-page[name="${currentSpaceDataAttr}"] > p.text {font-family: ${newValue};} parchment-page[name="${currentSpaceDataAttr}"] > file-::before, parchment-page[name="${currentSpaceDataAttr}"] > image-::before, parchment-page[name="${currentSpaceDataAttr}"] > parchment-page::before {font-family: ${newValue};font-weight: 400;} parchment-page[name="${currentSpaceDataAttr}"] > space-select-outline > file-::before, parchment-page[name="${currentSpaceDataAttr}"] > select-outline > *, parchment-page[name="${currentSpaceDataAttr}"] > select-outline > image-::before ,parchment-page[name="${currentSpaceDataAttr}"] > space-select-outline > parchment-page::before, parchment-page[name="${currentSpaceDataAttr}"] > a, parchment-page[name="${currentSpaceDataAttr}"] > input-box {font-family: ${newValue}; font-weight: 400;}`
cssIncomingText += cssInit
let CSSRawData = `REQUEST|update-css|${currentFolder}|${cssIncomingText}|`
await this.checkOpen()
this.ws.send(CSSRawData)
}
createCSSLink = (wsMessage) => {
let retrieveHref = wsMessage;
var link = document.createElement("link");
link.rel = "stylesheet";
link.id = window.wrapper.getAttribute('name')+"-css"
link.href = retrieveHref;
let retrieveStyleLinks = document.querySelectorAll(`[href='${retrieveHref}']`);
if (retrieveStyleLinks[0] !== undefined) {
retrieveStyleLinks[0].remove();
}
window.wrapper.prepend(link);
}
scrapeExternalHTMLTitle = async (href) => {
let req = `REQUEST|scrape-title|${href}|`
await this.checkOpen()
this.ws.send(req)
}
updateLinkTitle = (title) => {
let link = document.querySelectorAll('.convert-to-title')[0]
if (title !=="") {
link.innerHTML += title;
} else {
link.innerHTML += link.getAttribute('href')
}
link.classList.remove('convert-to-title')
}
}
export default Connection

View File

@@ -1,22 +0,0 @@
import Connection from "./Connection.js";
export default class ConnectionHandler {
connection;
disabled = true;
constructor() {
this.connection = new Connection();
}
isOpen() {
if(this.connection.checkOpen()) {
return true;
} else {
return false;
}
}
send(msg) {
this.connection.sendMessage(msg)
}
}

46
ui/site/ws/Socket.js Normal file
View File

@@ -0,0 +1,46 @@
import Connection from "./Connection.js";
export default class Socket {
connection;
disabled = true;
requestID = 1;
pending = new Map();
constructor() {
this.connection = new Connection(this.receive);
}
isOpen() {
if(this.connection.checkOpen()) {
return true;
} else {
return false;
}
}
send(msg) {
return new Promise(resolve => {
const id = (++this.requestID).toString();
this.pending.set(id, resolve);
this.connection.send(JSON.stringify({ id, ...msg }));
});
}
receive = (event) => {
const msg = JSON.parse(event.data);
if (msg.id && this.pending.has(msg.id)) {
this.pending.get(msg.id)(msg);
this.pending.delete(msg.id);
return;
} else {
this.onBroadcast(msg)
}
}
onBroadcast(msg) {
console.log(msg.msg)
window.dispatchEvent(new CustomEvent(msg.event, {
detail: msg.msg
}));
}
}