init
This commit is contained in:
commit
2f7c19f28e
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
data/*
|
||||
mlread
|
||||
|
1
data/.gitkeep
Normal file
1
data/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
|
9
deno.jsonc
Normal file
9
deno.jsonc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"fmt": {
|
||||
"indentWidth": 4
|
||||
},
|
||||
"tasks": {
|
||||
"run": "deno run --allow-net --allow-env=PORT --allow-read=./public,./data --allow-write=./public,./data main.ts",
|
||||
"compile": "deno compile --allow-net --allow-env=PORT --allow-read=./public,./data --allow-write=./public,./data --output mlread main.ts"
|
||||
}
|
||||
}
|
80
deno.lock
generated
Normal file
80
deno.lock
generated
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@oak/commons@1": "1.0.0",
|
||||
"jsr:@oak/oak@*": "17.1.3",
|
||||
"jsr:@std/assert@1": "1.0.8",
|
||||
"jsr:@std/bytes@1": "1.0.4",
|
||||
"jsr:@std/bytes@^1.0.2": "1.0.4",
|
||||
"jsr:@std/crypto@1": "1.0.3",
|
||||
"jsr:@std/encoding@1": "1.0.5",
|
||||
"jsr:@std/encoding@^1.0.5": "1.0.5",
|
||||
"jsr:@std/http@1": "1.0.10",
|
||||
"jsr:@std/io@0.224": "0.224.9",
|
||||
"jsr:@std/media-types@1": "1.1.0",
|
||||
"jsr:@std/path@1": "1.0.8",
|
||||
"npm:path-to-regexp@6.2.1": "6.2.1"
|
||||
},
|
||||
"jsr": {
|
||||
"@oak/commons@1.0.0": {
|
||||
"integrity": "49805b55603c3627a9d6235c0655aa2b6222d3036b3a13ff0380c16368f607ac",
|
||||
"dependencies": [
|
||||
"jsr:@std/assert",
|
||||
"jsr:@std/bytes@1",
|
||||
"jsr:@std/crypto",
|
||||
"jsr:@std/encoding@1",
|
||||
"jsr:@std/http",
|
||||
"jsr:@std/media-types"
|
||||
]
|
||||
},
|
||||
"@oak/oak@17.1.3": {
|
||||
"integrity": "d89296c22db91681dd3a2a1e1fd14e258d0d5a9654de55637aee5b661c159f33",
|
||||
"dependencies": [
|
||||
"jsr:@oak/commons",
|
||||
"jsr:@std/assert",
|
||||
"jsr:@std/bytes@1",
|
||||
"jsr:@std/crypto",
|
||||
"jsr:@std/http",
|
||||
"jsr:@std/io",
|
||||
"jsr:@std/media-types",
|
||||
"jsr:@std/path",
|
||||
"npm:path-to-regexp"
|
||||
]
|
||||
},
|
||||
"@std/assert@1.0.8": {
|
||||
"integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b"
|
||||
},
|
||||
"@std/bytes@1.0.4": {
|
||||
"integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc"
|
||||
},
|
||||
"@std/crypto@1.0.3": {
|
||||
"integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f"
|
||||
},
|
||||
"@std/encoding@1.0.5": {
|
||||
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
|
||||
},
|
||||
"@std/http@1.0.10": {
|
||||
"integrity": "4e32d11493ab04e3ef09f104f0cb9beb4228b1d4b47c5469573c2c294c0d3692",
|
||||
"dependencies": [
|
||||
"jsr:@std/encoding@^1.0.5"
|
||||
]
|
||||
},
|
||||
"@std/io@0.224.9": {
|
||||
"integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3",
|
||||
"dependencies": [
|
||||
"jsr:@std/bytes@^1.0.2"
|
||||
]
|
||||
},
|
||||
"@std/media-types@1.1.0": {
|
||||
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
|
||||
},
|
||||
"@std/path@1.0.8": {
|
||||
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"path-to-regexp@6.2.1": {
|
||||
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
|
||||
}
|
||||
}
|
||||
}
|
50
main.ts
Normal file
50
main.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import * as oak from "jsr:@oak/oak";
|
||||
|
||||
const port = Number(Deno.env.get("PORT") ?? 8000);
|
||||
|
||||
const router = new oak.Router();
|
||||
|
||||
router.post("/api/log", async (ctx) => {
|
||||
const data = await ctx.request.body.json();
|
||||
|
||||
const enc = new TextEncoder();
|
||||
|
||||
const postLog = await Deno.open("./data/log.txt", {
|
||||
append: true,
|
||||
create: true,
|
||||
});
|
||||
postLog.write(
|
||||
enc.encode(`${new Date().toISOString()}\t${JSON.stringify(data)}\n`),
|
||||
);
|
||||
|
||||
ctx.response.body = { ok: true };
|
||||
ctx.respond = true;
|
||||
});
|
||||
|
||||
router.get("/api/log", async (ctx) => {
|
||||
const postLog = await Deno.readTextFile("./data/log.txt");
|
||||
const entries = postLog
|
||||
.split("\n")
|
||||
.filter((l) => l)
|
||||
.map((entry) => entry.split("\t"))
|
||||
.map(([timestamp, data]) => (console.log(timestamp, data), {
|
||||
timestamp: new Date(timestamp),
|
||||
data: JSON.parse(data),
|
||||
}));
|
||||
ctx.response.body = entries;
|
||||
ctx.respond = true;
|
||||
});
|
||||
|
||||
console.log(`listening at http://localhost:${port}/`);
|
||||
|
||||
new oak.Application()
|
||||
.use(router.routes())
|
||||
.use(router.allowedMethods())
|
||||
.use(async (ctx) => {
|
||||
const { request: { url: { pathname } } } = ctx;
|
||||
await oak.send(ctx, pathname, {
|
||||
root: "./public",
|
||||
index: "index.html",
|
||||
});
|
||||
})
|
||||
.listen({ port });
|
14
mlread.service
Normal file
14
mlread.service
Normal file
@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=mlread webservice
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=simone
|
||||
ExecStart=/home/simone/mlread/mlread
|
||||
WorkingDirectory=/home/simone/mlread/
|
||||
Restart=on-failure
|
||||
Environment="PORT=8300"
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
9
public/deno.jsonc
Normal file
9
public/deno.jsonc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"fmt": {
|
||||
"indentWidth": 4
|
||||
},
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
|
||||
},
|
||||
|
||||
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
56
public/index.html
Normal file
56
public/index.html
Normal file
@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>philopsher's ml reading</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="index.js" type="module" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>philopsher's ml reading group</h1>
|
||||
<p>The plan is to read and discuss texts from the <b>ML test</b> materials in the <b>#test-materials</b> channels in FinBol's. (<a href="https://discord.com/channels/384015097284001792/874424650057257022" target="_blank">Link</a>)</p>
|
||||
<h2>The Three Sources and Three Component Parts of Marxism by Vladimir Lenin</h2>
|
||||
<p><b>To be scheduled...</b></p>
|
||||
<p>This text is available both <a href="https://www.marxists.org/archive/lenin/works/1913/mar/x01.htm" target="_blank">as text at MIA</a> and <a href="https://www.youtube.com/watch?v=MOAII71GaFY" target="_blank">as audiobook by Socialism For All</a>.</p>
|
||||
<p>The work can be read in 20 minutes. The plan is to start the event 20 minutes <b>before</b> the announced time, to give people time to read the work, if they haven't already. Then <b>at</b> the announced time, the presentation/discussion will start. I will go through what I think are the main points, and we will use this presentation as foundation for discussion.</p>
|
||||
<p>I may put some notes here afterwards...</p>
|
||||
<h1>Scheduler</h1>
|
||||
<div id="scheduler">
|
||||
<p>
|
||||
Select as many options as possible. I will try and find the
|
||||
best timeslot.
|
||||
</p>
|
||||
<p>
|
||||
Timestamps are UTC, which is currently <b><span id="time-utc">00:00</span></b>. Compensate according to your own timezone.
|
||||
</p>
|
||||
<form action="#" id="schedule-form">
|
||||
<div id="schedule-table-scroller">
|
||||
<table id="schedule-table">
|
||||
<tr><th>loading...</th></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="tail">
|
||||
<div>
|
||||
<label for="input-username">Discord username:</label>
|
||||
<input type="text" name="username" id="input-username">
|
||||
</div>
|
||||
<div>
|
||||
<label for="input-note">Note (optional):</label>
|
||||
<textarea name="note" id="input-note" rows="2" cols="30"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="tos">I will jack your shit.</label>
|
||||
<input type="checkbox" name="tos" id="tos">
|
||||
</div>
|
||||
</div>
|
||||
<div id="submit-div">
|
||||
<input type="submit" value="Submit">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
<script></script>
|
||||
</body>
|
||||
</html>
|
108
public/index.js
Normal file
108
public/index.js
Normal file
@ -0,0 +1,108 @@
|
||||
const timeUtcSpan = document.querySelector("#time-utc");
|
||||
const scheduleTable = document.querySelector("#schedule-table");
|
||||
const scheduleForm = document.querySelector("#schedule-form");
|
||||
|
||||
timeUtcSpan.innerText = new Date()
|
||||
.toUTCString()
|
||||
.slice(17, 22);
|
||||
// .toLocaleTimeString(["en-UK"], { hour: "2-digit", minute: "2-digit" });
|
||||
|
||||
const weekDayNames = [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday",
|
||||
];
|
||||
|
||||
const monthNames = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
|
||||
async function makeScheduleTable() {
|
||||
const timeslotData = await fetch("timeslots.json")
|
||||
.then((res) => res.json());
|
||||
|
||||
const timeslots = timeslotData
|
||||
.map(({ date, times }) => ({
|
||||
dateString: date,
|
||||
date: new Date(date),
|
||||
times,
|
||||
}))
|
||||
.toSorted((a, b) => a.date.getTime() - b.date.getTime());
|
||||
|
||||
let tableHtml = `<tr>
|
||||
${
|
||||
timeslots.map((ts) =>
|
||||
`<th>${weekDayNames[ts.date.getUTCDay()]} ${ts.date.getUTCDay()}. ${
|
||||
monthNames[ts.date.getUTCMonth()]
|
||||
}</th>`
|
||||
)
|
||||
.join("")
|
||||
}
|
||||
</tr>`;
|
||||
|
||||
tableHtml += "<tr>";
|
||||
for (const timeslot of timeslots) {
|
||||
tableHtml += "<td>";
|
||||
for (const time of timeslot.times) {
|
||||
const name = `${timeslot.dateString}-${time}`;
|
||||
tableHtml += `
|
||||
<div>
|
||||
<input type="checkbox" name="${name}" id="checkbox-${name}">
|
||||
<label for="checkbox-${name}">${time}</label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
tableHtml += "</td>";
|
||||
}
|
||||
tableHtml += "</tr>";
|
||||
|
||||
scheduleTable.innerHTML = tableHtml;
|
||||
}
|
||||
|
||||
scheduleForm.onsubmit = async (ev) => {
|
||||
ev.preventDefault();
|
||||
const formdata = new FormData(ev.target);
|
||||
const data = formdata.entries().reduce(
|
||||
(acc, [key, val]) => ({ ...acc, [key]: val }),
|
||||
{},
|
||||
);
|
||||
if (!data.username) {
|
||||
alert("please specify username");
|
||||
return;
|
||||
}
|
||||
if (!data.tos) {
|
||||
alert("please check information agreement");
|
||||
return;
|
||||
}
|
||||
console.log(data);
|
||||
|
||||
const response = await fetch("/api/log", {
|
||||
method: "post",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => res.json());
|
||||
|
||||
if (response.ok) {
|
||||
alert("submitted!");
|
||||
} else {
|
||||
alert("error occured!");
|
||||
console.log(response);
|
||||
}
|
||||
};
|
||||
|
||||
makeScheduleTable();
|
69
public/style.css
Normal file
69
public/style.css
Normal file
@ -0,0 +1,69 @@
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
max-width: 1000px;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 1ex;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
border: 1px solid;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#scheduler {
|
||||
border: 1px solid;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#scheduler form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#scheduler form #tail {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
align-items: start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#scheduler #submit-div {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#scheduler form input[type="submit"] {
|
||||
width: 30%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#schedule-table-scroller {
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#schedule-table {
|
||||
width: max-content;
|
||||
}
|
21
public/timeslots.json
Normal file
21
public/timeslots.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{ "date": "7-1-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-2-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-3-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-4-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-5-2025", "times": [] },
|
||||
{ "date": "7-6-2025", "times": ["13:00", "15:00", "18:00", "21:00"] },
|
||||
{ "date": "7-7-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-8-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-9-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-10-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-11-2025", "times": [] },
|
||||
{
|
||||
"date": "7-12-2025",
|
||||
"times": ["13:00", "15:00", "18:00", "21:00", "23:00"]
|
||||
},
|
||||
{ "date": "7-13-2025", "times": ["13:00", "15:00", "18:00", "21:00"] },
|
||||
{ "date": "7-14-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-15-2025", "times": ["18:00", "21:00"] },
|
||||
{ "date": "7-16-2025", "times": ["18:00", "21:00"] }
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user