Ability to post in Forum
This commit is contained in:
@@ -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
8
ui/_/code/zod.js
Normal file
File diff suppressed because one or more lines are too long
@@ -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)
|
||||
83
ui/site/apps/Forum/MessagesPanel.js
Normal file
83
ui/site/apps/Forum/MessagesPanel.js
Normal 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)
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = ""
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
46
ui/site/ws/Socket.js
Normal 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
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user