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

@@ -3,7 +3,7 @@
"HY-1": {
"fullName": "Founder"
},
"MEMBER-samrussell99@pm.me": {
"MEMBER-1": {
"email": "samrussell99@pm.me",
"firstName": "Sam",
"lastName": "Russell",
@@ -317,6 +317,11 @@
"url": "https://hyperia.so/signup?token=",
"uuid": "f2016b40-a179-47ec-a415-84caa7da8521",
"used": false
},
"POST-HY-1": {
"text": "This is the first message ever.",
"time": "11.24.2025-12:54:360784am",
"sentBy": "MEMBER-1"
}
},
"edges": {}

View File

@@ -2,6 +2,8 @@
const { app, BrowserWindow, nativeImage } = require('electron');
const path = require('path');
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
function createWindow() {
const win = new BrowserWindow({
show: false, // window is hidden

View File

@@ -28,6 +28,21 @@ export default class AuthHandler {
}
}
getProfile(req, res) {
const token = req.cookies.auth_token;
if (!token) return res.status(401).send({ error: "No auth token" });
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
const email = payload.email;
const user = db.members.getByEmail(email);
res.send({ email: user.email, name: user.firstName + " " + user.lastName });
} catch (err) {
res.status(401).send({ error: "Invalid token" });
}
}
async login(req, res) {
const { email, password } = req.body;
let foundUser = global.db.members.getByEmail(email)
@@ -40,7 +55,8 @@ export default class AuthHandler {
if (!valid) {
res.status(400).json({ error: 'Incorrect password.' });
} else {
const payload = { id: foundUser.id };
const payload = { email: foundUser.email };
console.log(payload)
const secret = process.env.JWT_SECRET;
const options = { expiresIn: "2h" };
const token = jwt.sign(payload, secret, options);

View File

@@ -5,16 +5,19 @@ import QuillDB from "../_/quilldb.js"
import Titles from "./model/Titles.js"
import Members from './model/Members.js'
import Tokens from './model/Tokens.js'
import Posts from "./model/Posts.js"
export default class Database {
titles = new Titles()
members = new Members()
tokens = new Tokens()
posts = new Posts()
fromID = {
"HY": this.titles,
"MEMBER": this.members,
"TOKEN": this.tokens
"TOKEN": this.tokens,
"POST": this.posts
}
constructor() {
@@ -39,7 +42,7 @@ export default class Database {
try {
let collection = this.fromID[type]
if(collection) {
collection.save(node)
collection.save(node, id)
} else {
throw new Error("Type does not exist for node: ", id)
}
@@ -63,12 +66,14 @@ export default class Database {
let arrs = [
this.titles.entries,
this.members.entries,
this.tokens.entries
this.tokens.entries,
this.posts.entries
]
let ids = [
Object.entries(this.titles.ids),
Object.entries(this.members.ids),
Object.entries(this.tokens.ids),
Object.entries(this.posts.ids),
]
for(let i=0; i<arrs.length; i++) {
let arr = arrs[i]

View File

@@ -26,7 +26,7 @@ export default class Members extends OrderedObject {
isHashed = (s) => {return s.startsWith("$argon2")}
save(member) {
let id = `MEMBER-${member.email}`
let id = `MEMBER-${this.entries.length+1}`
let result = this.schema.safeParse(member)
if(result.success) {
try {
@@ -45,29 +45,31 @@ export default class Members extends OrderedObject {
newMember.tokenUsed = tokenID
const hash = await argon2.hash(newMember.password);
newMember.password = hash
newMember.joined = this.currentTime()
newMember.joined = global.currentTime()
this.save(newMember)
}
getByEmail(email) {
return super.get(`MEMBER-${email}`)
get(id) {
return this.entries[this.ids[id]]
}
currentTime() {
const now = new Date();
getByEmail(email) {
for(let i=0; i<this.entries.length; i++) {
if(this.entries[i].email === email) {
return this.entries[i]
}
}
return null
}
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
const year = now.getFullYear();
let hours = now.getHours();
const ampm = hours >= 12 ? "pm" : "am";
hours = hours % 12 || 12; // convert to 12-hour format
const minutes = String(now.getMinutes()).padStart(2, "0");
const seconds = String(now.getSeconds()).padStart(2, "0");
const ms = String(now.getMilliseconds()).padStart(4, "0"); // 4-digit like "5838"
return `${month}.${day}.${year}-${hours}:${minutes}:${seconds}${ms}${ampm}`;
getIDFromEmail(email) {
let index = 0
for(let i=0; i<this.entries.length; i++) {
if(this.entries[i].email === email) {
index = i
break
}
}
return Object.entries(this.ids)[index][0]
}
}

View File

@@ -1,9 +1,10 @@
export default class OrderedObject {
entries = []
ids = {}
indexes = []
add(id, data) {
if(this.get(id)) {
if(this.ids[id]) {
console.error(`Can't add item ${id}: id already exists`)
throw new global.ServerError(400, `Member with this email already exists`)
}
@@ -23,12 +24,4 @@ export default class OrderedObject {
return this.entries[this.ids[key]]
}
}
get(key) {
if (typeof key === "number") {
return this.entries[key]
} else {
return this.entries[this.ids[key]]
}
}
}

56
server/db/model/Posts.js Normal file
View File

@@ -0,0 +1,56 @@
import OrderedObject from "./OrderedObject.js"
const { z } = require("zod")
export default class Posts extends OrderedObject {
schema = z.object({
text: z.string(),
time: z.string(),
sentBy: z.string()
})
makeID(forum, number) {
return `POST-${forum}-${number}`
}
save(post, id) {
let result = this.schema.safeParse(post)
if(result.success) {
try {
super.add(id, post)
} catch(e) {
console.error(e)
throw e
}
} else {
console.error("Failed parsing member: ", result.error)
throw new global.ServerError(400, "Invalid Member Data!: ");
}
}
get(forum, number) {
let result = []
let limit = Math.min(number, this.entries.length)
for(let i=1; i<=limit; i++) {
let id = this.makeID(forum, i)
let post = this.entries[this.ids[id]]
let {firstName, lastName} = global.db.members.get(post.sentBy)
let seededObj = {
...post
}
seededObj.sentByID = post.sentBy
seededObj.sentBy = firstName + " " + lastName
result.push(seededObj)
}
return result
}
async add(text, forum, userEmail) {
let newPost = {}
newPost.text = text
newPost.sentBy = db.members.getIDFromEmail(userEmail)
newPost.time = global.currentTime()
let idNumber = this.entries.length+1
super.add(this.makeID(forum, idNumber), newPost)
}
}

View File

@@ -4,7 +4,6 @@ export default class Titles extends OrderedObject {
save(newTitle) {
let id = `HY-${this.entries.length+1}`
console.log(id)
if(this.validate(id, newTitle)) {
try {
super.add(id, newTitle)

View File

@@ -36,6 +36,6 @@ export default class Tokens extends OrderedObject {
}
get(uuid) {
return super.get(`TOKEN-${uuid}`)
return this.entries[this.ids[`TOKEN-${uuid}`]]
}
}

View File

@@ -1,12 +0,0 @@
import { broadcast } from './ws.js';
const handlers = {
updateLocation(req, res) {
const { name, latitude, longitude, timestamp } = req.body;
console.log(`Received location: (${name}, ${latitude}, ${longitude}) at ${timestamp}`);
broadcast("update-location", { name, latitude, longitude, timestamp });
res.json({ success: true });
}
}
export default handlers;

View File

@@ -7,10 +7,10 @@ const chalk = require('chalk');
const moment = require('moment');
const path = require('path');
import { initWebSocket } from './ws.js'
import "./util.js"
import Socket from './ws/ws.js'
import Database from "./db/db.js"
import AuthHandler from './auth.js';
import handlers from "./handlers.js";
class Server {
db;
@@ -21,6 +21,7 @@ class Server {
registerRoutes(router) {
// router.post('/api/location', handlers.updateLocation)
router.post('/login', this.auth.login)
router.get('/profile', this.auth.getProfile)
router.get('/signout', this.auth.logout)
router.get('/signup', this.verifyToken, this.get)
router.post('/signup', this.verifyToken, this.newUserSubmission)
@@ -171,8 +172,13 @@ class Server {
this.registerRoutes(router)
app.use('/', router);
setInterval(() => {
console.log("saving db")
global.db.saveData()
}, 5000)
const server = http.createServer(app);
initWebSocket(server);
global.Socket = new Socket(server);
const PORT = 3003;
server.listen(PORT, () => {
console.logclean("\n")
@@ -219,11 +225,4 @@ console.logclean = function (...args) {
// _log.call(console, `[${location}]`, ...args);
// };
global.ServerError = class extends Error {
constructor(status, msg) {
super(msg);
this.status = status;
}
}
const server = new Server()

24
server/util.js Normal file
View File

@@ -0,0 +1,24 @@
global.ServerError = class extends Error {
constructor(status, msg) {
super(msg);
this.status = status;
}
}
global.currentTime = function () {
const now = new Date();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
const year = now.getFullYear();
let hours = now.getHours();
const ampm = hours >= 12 ? "pm" : "am";
hours = hours % 12 || 12; // convert to 12-hour format
const minutes = String(now.getMinutes()).padStart(2, "0");
const seconds = String(now.getSeconds()).padStart(2, "0");
const ms = String(now.getMilliseconds()).padStart(4, "0"); // 4-digit like "5838"
return `${month}.${day}.${year}-${hours}:${minutes}:${seconds}${ms}${ampm}`;
}

View File

@@ -1,31 +0,0 @@
const { WebSocket, WebSocketServer } = require('ws');
let wss;
export function initWebSocket(server) {
wss = new WebSocketServer({ server });
wss.on('connection', (ws, req) => {
console.log('✅ New WebSocket client connected');
ws.on('close', () => {
console.log('Client disconnected');
});
});
console.log('WebSocket server initialized');
}
// Broadcast a message to all connected clients
export function broadcast(reqType, data) {
if (!wss) return;
const payload = typeof data === 'string' ? data : JSON.stringify(data);
const message = `${reqType}|${payload}`;
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}

View File

@@ -0,0 +1,43 @@
const { z } = require("zod")
const sendSchema = z.object({
forum: z.string(),
text: z.string(),
})
.strict()
const getSchema = z.object({
forum: z.string(),
number: z.number()
})
.strict()
export default class ForumHandler {
static handleSend(msg, ws) {
try {
global.db.posts.add(msg.text, msg.forum, ws.userEmail)
global.Socket.broadcast({event: "new-message", app: "FORUM", forum: msg.forum, msg: this.handleGet({forum: msg.forum, number: 100})})
return {success: true}
} catch(e) {
console.error(e)
}
}
static handleGet(msg) {
let data = global.db.posts.get(msg.forum, msg.number)
return data
}
static handle(operation, msg, ws) {
switch(operation) {
case "SEND":
if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
return this.handleSend(msg, ws)
case "GET":
if(!getSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
return this.handleGet(msg)
}
}
}

93
server/ws/ws.js Normal file
View File

@@ -0,0 +1,93 @@
const { WebSocket, WebSocketServer } = require('ws');
const { z } = require("zod")
const jwt = require('jsonwebtoken');
import ForumHandler from "./handlers/ForumHandler.js"
export default class Socket {
wss;
messageSchema = z.object({
id: z.string(),
app: z.string(),
operation: z.string().optional(),
msg: z.union([
z.object({}).passthrough(), // allows any object
z.array(z.any()) // allows any array
])
}).strict()
constructor(server) {
this.wss = new WebSocketServer({ server });
this.wss.on('connection', (ws, req) => {
console.log('✅ New WebSocket client connected');
function parseCookies(cookieHeader = "") {
return Object.fromEntries(
cookieHeader.split(";").map(c => {
const [key, ...v] = c.trim().split("=");
return [key, decodeURIComponent(v.join("="))];
})
);
}
const cookies = parseCookies(req.headers.cookie);
const token = cookies.auth_token;
if (!token) throw new Error("No auth token");
const payload = jwt.verify(token, process.env.JWT_SECRET);
ws.userEmail = payload.email;
ws.on('message', (msg) => {
this.handleMessage(msg, ws);
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
console.log('WebSocket server initialized');
}
// Build a system where the ws obj is updated every time on navigate, so it already has context
// this way, we can only send broadcast messages to clients that actually have that app / subapp open
handleMessage = (msg, ws) => {
try {
const text = msg.toString();
const req = JSON.parse(text);
if(!this.messageSchema.safeParse(req).success) throw new Error("Socket.handleMessage: Incorrectly formatted incoming ws message!")
let responseData;
switch (req.app) {
case "FORUM":
responseData = ForumHandler.handle(req.operation, req.msg, ws)
break;
default:
console.error("unknown ws message")
}
let response = {
...req
}
response.msg = responseData
if(!this.messageSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Incorrectly formatted outgoing ws message!")
ws.send(JSON.stringify(response))
} catch (e) {
console.error("Invalid WS message:", e);
}
}
broadcast(event) {
if (!this.wss) return;
let message = JSON.stringify(event)
this.wss.clients.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
});
}
}

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
}));
}
}