some progress
This commit is contained in:
parent
13f63c7700
commit
1626734c1a
|
@ -4,8 +4,12 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aes-gcm = { version = "0.10.3", default-features = false, features = ["aes", "rand_core"] }
|
||||
ed25519-dalek = { version = "2.1.1", default-features = false, features = ["rand_core"] }
|
||||
anyhow = "1.0.89"
|
||||
axum = "0.7.7"
|
||||
getrandom = "0.2.15"
|
||||
rand_core = { version = "0.6.4", features = ["getrandom"] }
|
||||
x25519-dalek = { version = "2.0.1", default-features = false }
|
||||
htmlm = "0.3.0"
|
||||
log = "0.4.22"
|
||||
rusqlite = "0.32.1"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
time = "0.3.36"
|
||||
tokio = { version = "1.40.0", features = ["rt"] }
|
||||
|
|
88
depell/src/index.css
Normal file
88
depell/src/index.css
Normal file
|
@ -0,0 +1,88 @@
|
|||
* {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
--primary: white;
|
||||
--secondary: #EFEFEF;
|
||||
--error: #ff3333;
|
||||
--placeholder: #333333;
|
||||
}
|
||||
|
||||
body {
|
||||
--small-gap: 5px;
|
||||
--monospace: 'Courier New', Courier, monospace;
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
section:last-child {
|
||||
display: flex;
|
||||
gap: var(--small-gap);
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: var(--small-gap);
|
||||
}
|
||||
}
|
||||
|
||||
div.preview {
|
||||
div.stats {
|
||||
display: flex;
|
||||
gap: var(--small-gap);
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--small-gap);
|
||||
|
||||
::placeholder {
|
||||
color: var(--placeholder);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pre,
|
||||
textarea {
|
||||
outline: none;
|
||||
border: none;
|
||||
background: var(--secondary);
|
||||
padding: var(--small-gap);
|
||||
padding-top: calc(var(--small-gap) * 1.5);
|
||||
margin: var(--small-gap) 0px;
|
||||
font-family: var(--monospace);
|
||||
resize: none;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: inherit;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: var(--secondary);
|
||||
padding: var(--small-gap);
|
||||
}
|
||||
|
||||
input:is(:hover, :focus) {
|
||||
background: white;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: inherit;
|
||||
background: var(--secondary);
|
||||
}
|
||||
|
||||
button:hover:not(:active) {
|
||||
background: white;
|
||||
}
|
95
depell/src/index.js
Normal file
95
depell/src/index.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
//// @ts-check
|
||||
|
||||
if (window.location.hostname === 'localhost') {
|
||||
let id; setInterval(async () => {
|
||||
let new_id = await fetch('/hot-reload').then(reps => reps.text());
|
||||
id ??= new_id;
|
||||
if (id !== new_id) window.location.reload();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
document.body.addEventListener('htmx:afterSwap', (ev) => {
|
||||
wireUp(ev.target);
|
||||
});
|
||||
|
||||
wireUp(document.body);
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function wireUp(target) {
|
||||
execApply(target);
|
||||
cacheInputs(target);
|
||||
bindTextareaAutoResize(target);
|
||||
}
|
||||
|
||||
/** @param {string} content @return {string} */
|
||||
function fmtTimestamp(content) {
|
||||
new Date(parseInt(content) * 1000).toLocaleString()
|
||||
}
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function execApply(target) {
|
||||
/**@type {HTMLElement}*/ let elem;
|
||||
for (elem of target.querySelectorAll('[apply]')) {
|
||||
const funcname = elem.getAttribute('apply');
|
||||
elem.textContent = window[funcname](elem.textContent);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function bindTextareaAutoResize(target) {
|
||||
/**@type {HTMLTextAreaElement}*/ let textarea;
|
||||
for (textarea of target.querySelectorAll("textarea")) {
|
||||
textarea.style.height = textarea.scrollHeight + "px";
|
||||
textarea.style.overflowY = "hidden";
|
||||
textarea.addEventListener("input", function() {
|
||||
textarea.style.height = "auto";
|
||||
textarea.style.height = textarea.scrollHeight + "px";
|
||||
});
|
||||
|
||||
textarea.onkeydown = (ev) => {
|
||||
const selecting = textarea.selectionStart !== textarea.selectionEnd;
|
||||
|
||||
if (ev.key === "Tab") {
|
||||
ev.preventDefault();
|
||||
const prevPos = textarea.selectionStart;
|
||||
textarea.value = textarea.value.slice(0, textarea.selectionStart) +
|
||||
' ' + textarea.value.slice(textarea.selectionEnd);
|
||||
textarea.selectionStart = textarea.selectionEnd = prevPos + 4;
|
||||
}
|
||||
|
||||
if (ev.key === "Backspace" && textarea.selectionStart != 0 && !selecting) {
|
||||
let i = textarea.selectionStart, looped = false;
|
||||
while (textarea.value.charCodeAt(--i) === ' '.charCodeAt(0)) looped = true;
|
||||
if (textarea.value.charCodeAt(i) === '\n'.charCodeAt(0) && looped) {
|
||||
ev.preventDefault();
|
||||
let toDelete = (textarea.selectionStart - (i + 1)) % 4;
|
||||
if (toDelete === 0) toDelete = 4;
|
||||
const prevPos = textarea.selectionStart;
|
||||
textarea.value = textarea.value.slice(0, textarea.selectionStart - toDelete) +
|
||||
textarea.value.slice(textarea.selectionEnd);
|
||||
textarea.selectionStart = textarea.selectionEnd = prevPos - toDelete;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function cacheInputs(target) {
|
||||
/**@type {HTMLFormElement}*/ let form;
|
||||
for (form of target.querySelectorAll('form')) {
|
||||
const path = form.getAttribute('hx-post') || form.getAttribute('hx-delete');
|
||||
if (!path) {
|
||||
console.warn('form does not have a hx-post or hx-delete attribute', form);
|
||||
continue;
|
||||
}
|
||||
|
||||
/**@type {HTMLInputElement}*/ let input;
|
||||
for (input of form.elements) {
|
||||
if ('password submit button'.includes(input.type)) continue;
|
||||
const key = path + input.name;
|
||||
input.value = localStorage.getItem(key) ?? '';
|
||||
input.addEventListener("input", (ev) => localStorage.setItem(key, ev.target.value));
|
||||
}
|
||||
}
|
||||
}
|
1094
depell/src/main.rs
1094
depell/src/main.rs
File diff suppressed because it is too large
Load diff
12
depell/src/post-page.html
Normal file
12
depell/src/post-page.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<h3>About posting code</h3>
|
||||
<p>
|
||||
If you are unfammiliar with <a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a>, refer to the
|
||||
<strong>hblang/README.md</strong> or
|
||||
vizit <a href="/profile/mlokis">mlokis'es posts</a>. Preferably don't edit the code here.
|
||||
</p>
|
||||
|
||||
<h3>Extra textarea features</h3>
|
||||
<ul>
|
||||
<li>proper tab behaviour</li>
|
||||
<li>snap to previous tab boundary on "empty" lines</li>
|
||||
</ul>
|
0
depell/src/profile-page.html
Normal file
0
depell/src/profile-page.html
Normal file
51
depell/src/schema.sql
Normal file
51
depell/src/schema.sql
Normal file
|
@ -0,0 +1,51 @@
|
|||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user(
|
||||
name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
PRIMARY KEY (name)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session(
|
||||
id BLOB NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
expiration INTEGER NOT NULL,
|
||||
FOREIGN KEY (username) REFERENCES user (name)
|
||||
PRIMARY KEY (username)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS
|
||||
session_id ON session (id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS post(
|
||||
name TEXT NOT NULL,
|
||||
author TEXT,
|
||||
timestamp INTEGER,
|
||||
code TEXT NOT NULL,
|
||||
FOREIGN KEY (author) REFERENCES user (name) ON DELETE SET NULL,
|
||||
PRIMARY KEY (author, name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS import(
|
||||
from_name TEXT NOT NULL,
|
||||
from_author TEXT,
|
||||
to_name TEXT NOT NULL,
|
||||
to_author TEXT,
|
||||
FOREIGN KEY (from_name, from_author) REFERENCES post (name, author),
|
||||
FOREIGN KEY (to_name, to_author) REFERENCES post (name, author)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS
|
||||
dependencies ON import(from_name, from_author);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS
|
||||
dependants ON import(to_name, to_author);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS run(
|
||||
code_name TEXT NOT NULL,
|
||||
code_author TEXT NOT NULL,
|
||||
runner TEXT NOT NULL,
|
||||
FOREIGN KEY (code_name, code_author) REFERENCES post (name, author),
|
||||
FOREIGN KEY (runner) REFERENCES user(name),
|
||||
PRIMARY KEY (code_name, code_author, runner)
|
||||
);
|
17
depell/src/welcome-page.html
Normal file
17
depell/src/welcome-page.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<h1>Welcome to depell</h1>
|
||||
<p>
|
||||
Depell (dependency hell) is a simple "social" media site best compared to twitter, except that all you can post is
|
||||
<a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a> code with no comments allowed. Instead of likes you
|
||||
run the program, and instead of retweets you import the program as dependency. Run counts even when ran indirectly.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The backend only serves the code and frontend compiles and runs it locally. All posts are immutable.
|
||||
</p>
|
||||
|
||||
<h2>Security?</h2>
|
||||
<p>
|
||||
All code runs in WASM (inside a holey-bytes VM until hblang compiles to wasm) and is controlled by JavaScript. WASM
|
||||
cant do any form of IO without going trough JavaScript so as long as JS import does not allow wasm to execute
|
||||
arbitrary JS code, WASM can act as a container inside the JS.
|
||||
</p>
|
11
depell/wasm-hbfmt/Cargo.toml
Normal file
11
depell/wasm-hbfmt/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "wasm-hbfmt"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
hblang = { version = "0.1.0", path = "../../hblang", default-features = false }
|
||||
log = { version = "0.4.22", features = ["max_level_off"] }
|
101
depell/wasm-hbfmt/src/lib.rs
Normal file
101
depell/wasm-hbfmt/src/lib.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
#![no_std]
|
||||
#![feature(slice_take)]
|
||||
#![feature(str_from_raw_parts)]
|
||||
|
||||
use hblang::parser::ParserCtx;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[panic_handler]
|
||||
fn handle_panic(_: &core::panic::PanicInfo) -> ! {
|
||||
core::arch::wasm32::unreachable()
|
||||
}
|
||||
|
||||
use core::{
|
||||
alloc::{GlobalAlloc, Layout},
|
||||
cell::UnsafeCell,
|
||||
};
|
||||
|
||||
const ARENA_SIZE: usize = 128 * 1024;
|
||||
|
||||
#[repr(C, align(32))]
|
||||
struct SimpleAllocator {
|
||||
arena: UnsafeCell<[u8; ARENA_SIZE]>,
|
||||
head: UnsafeCell<*mut u8>,
|
||||
}
|
||||
|
||||
impl SimpleAllocator {
|
||||
const fn new() -> Self {
|
||||
SimpleAllocator {
|
||||
arena: UnsafeCell::new([0; ARENA_SIZE]),
|
||||
head: UnsafeCell::new(core::ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn reset(&self) {
|
||||
(*self.head.get()) = self.arena.get().cast::<u8>().add(ARENA_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for SimpleAllocator {}
|
||||
|
||||
unsafe impl GlobalAlloc for SimpleAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let size = layout.size();
|
||||
let align = layout.align();
|
||||
|
||||
let until = self.arena.get() as *mut u8;
|
||||
|
||||
let new_head = (*self.head.get()).sub(size);
|
||||
let aligned_head = (new_head as usize & (1 << (align - 1))) as *mut u8;
|
||||
|
||||
if until > aligned_head {
|
||||
return core::ptr::null_mut();
|
||||
}
|
||||
|
||||
*self.head.get() = aligned_head;
|
||||
aligned_head
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
|
||||
/* lol */
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", global_allocator)]
|
||||
static ALLOCATOR: SimpleAllocator = SimpleAllocator::new();
|
||||
|
||||
const MAX_OUTPUT_SIZE: usize = 1024 * 10;
|
||||
|
||||
#[no_mangle]
|
||||
static mut OUTPUT: [u8; MAX_OUTPUT_SIZE] = unsafe { core::mem::zeroed() };
|
||||
|
||||
#[no_mangle]
|
||||
static mut OUTPUT_LEN: usize = 0;
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn fmt(code: *const u8, len: usize) {
|
||||
ALLOCATOR.reset();
|
||||
|
||||
let code = core::str::from_raw_parts(code, len);
|
||||
|
||||
let arena = hblang::parser::Arena::default();
|
||||
let mut ctx = ParserCtx::default();
|
||||
let exprs = hblang::parser::Parser::parse(&mut ctx, code, "source.hb", &|_, _| Ok(0), &arena);
|
||||
|
||||
struct Write<'a>(&'a mut [u8]);
|
||||
|
||||
impl core::fmt::Write for Write<'_> {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
if let Some(m) = self.0.take_mut(..s.len()) {
|
||||
m.copy_from_slice(s.as_bytes());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(core::fmt::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut f = Write(unsafe { &mut OUTPUT[..] });
|
||||
hblang::fmt::fmt_file(exprs, code, &mut f).unwrap();
|
||||
unsafe { OUTPUT_LEN = MAX_OUTPUT_SIZE - f.0.len() };
|
||||
}
|
Loading…
Reference in a new issue