╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────╮ │1 const std = @import("std"); │ │2 const xit = @import("xit"); │ │3 const rp = xit.repo; │ │4 -const hash = xit.hash; │ │4 const evt = @import("./event.zig");│ │5 const ui = @import("./ui.zig"); │ │6 const xitui = xit.xitui; │ ╰──────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │149 return handleLogout(request, session_store); │ │150 } │ │151 if (method == .POST and std.mem.eql(u8, path, "/ansi")) { │ │153 - return handleAnsi(request); │ │152 + return handleAnsi(io, request, allocator, admin_repo_path, session_store);│ │153 } │ │154 │ │155 const get_or_head = method == .GET or method == .HEAD; │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │189 null │ │190 else │ │191 null; │ │192 + // enable_ansi is omitted here on purpose: Session.init fills it in from│ │193 + // the logged-in user's event, falling back to false when anonymous. │ │194 const html = try renderIndexHtml(io, allocator, admin_repo_path, .{ │ │195 .user_id = user_id, │ │196 .login_failure = login_failure, │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │197 .current_page = current_page, │ │197 - // enable_ansi is intentionally left at its default │ │198 }); │ │199 defer allocator.free(html); │ │200 // expire the flash cookie on the way out so a refresh doesn't keep│ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │248 var arena = std.heap.ArenaAllocator.init(allocator); │ │249 defer arena.deinit(); │ │250 │ │251 - const haxy_moment = try openHaxyMoment(repo_opts, &repo); │ │251 + const haxy_moment = try evt.currentMoment(repo_opts, &repo); │ │252 const result = try evt.User.verifyCredentials(Repo.DB, repo_opts.hash, haxy_moment, &arena, username, password);│ │253 │ │254 switch (result) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │304 }); │ │305 } │ │306 │ │307 -fn handleAnsi(request: *std.http.Server.Request) !void { │ │308 - // TODO: persist the flip in the logged-in user's server-side session so it │ │309 - // survives this redirect and subsequent GETs. │ │310 - // │ │307 +fn handleAnsi( │ │308 + io: std.Io, │ │309 + request: *std.http.Server.Request, │ │310 + allocator: std.mem.Allocator, │ │311 + admin_repo_path: []const u8, │ │312 + session_store: SessionStore, │ │313 +) !void { │ │314 + var user_id: [evt.event_id_size]u8 = undefined; │ │315 + if (getCookieValue(request, cookie_name)) |token| { │ │316 + if (session_store.lookup(token, &user_id)) { │ │317 + const repo_opts: rp.RepoOpts(.xit) = .{}; │ │318 + const Repo = rp.Repo(.xit, repo_opts); │ │319 + var repo = try Repo.open(io, allocator, .{ .path = admin_repo_path });│ │320 + defer repo.deinit(io, allocator); │ │321 + │ │322 + try evt.User.toggleAnsi(repo_opts, io, allocator, &repo, &user_id); │ │323 + } │ │324 + } │ │325 + │ │326 // like logout, this is a bodyless POST, so close the connection rather than │ │327 // letting the keep-alive path try to discard a body that isn't framed. │ │328 try request.respond("", .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │544 │ │545 try out.appendSlice(allocator, "<form action=\""); │ │546 try appendEscapedHtml(allocator, &out, action_url); │ │532 - try out.appendSlice(allocator, "\" method=\"post\">"); │ │547 + try out.appendSlice(allocator, "\" method=\"post\""); │ │548 + // an empty action must not POST — block it and send the input to the TUI instead │ │549 + if (action_url.len == 0) try out.appendSlice(allocator, " onsubmit=\"event.preventDefault();sendEnter(this);\"");│ │550 + try out.appendSlice(allocator, ">"); │ │551 │ │552 // form.focus.children was populated by addChild, which flattens the │ │553 // whole subtree into one map — so descendants (inputs + the submit │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │663 │ │664 // --- helpers -------------------------------------------------------------- │ │665 │ │648 -fn openHaxyMoment( │ │649 - comptime repo_opts: rp.RepoOpts(.xit), │ │650 - repo: *rp.Repo(.xit, repo_opts), │ │651 -) !rp.Repo(.xit, repo_opts).DB.HashMap(.read_only) { │ │652 - const DB = rp.Repo(.xit, repo_opts).DB; │ │653 - const history = try DB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); │ │654 - const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; │ │655 - const moment = try DB.HashMap(.read_only).init(moment_cursor); │ │656 - const last_object_id_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id")) orelse return error.NotFound;│ │657 - var last_object_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │658 - _ = try last_object_id_cursor.readBytes(&last_object_id); │ │659 - const haxy_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy")) orelse return error.NotFound; │ │660 - const haxy = try DB.ArrayList(.read_only).init(haxy_cursor); │ │661 - const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; │ │662 - const haxy_moments = try DB.HashMap(.read_only).init(haxy_moments_cursor); │ │663 - const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound;│ │664 - return try DB.HashMap(.read_only).init(haxy_moment_cursor); │ │665 -} │ │666 - │ │666 fn getCookieValue(request: *std.http.Server.Request, name: []const u8) ?[]const u8 { │ │667 var iter = request.iterateHeaders(); │ │668 while (iter.next()) |header| { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯