╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ web.zig ──────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
490 try appendEscapedHtml(allocator, &out, entry.ti.options.name); 491 } 492 // intentionally no `value` attribute: the browser tracks user input 493 // natively, and including the wasm-side value here would make the 494 // overlay HTML differ on every keystroke — that would trip the diff 495 // in _setOverlay and rebuild the <input>, eating the user's caret. 496 try out.appendSlice(allocator, "\" style=\"left:"); 497 var pos_buf: [64]u8 = undefined; 498 try out.appendSlice(allocator, try std.fmt.bufPrint(&pos_buf, "{d}ch;top:{d}em;width:{d}ch;height:1em", .{ inner_left, inner_top, inner_width })); 499 try out.appendSlice(allocator, "\">"); 500 } 501 502 if (submit) |btn| { 503 // inside the dashed border the TUI cells draw around the button. 504 const inner_left = btn.x + 1; 505 const inner_top = btn.y + 1; 506 const inner_width = if (btn.width > 2) btn.width - 2 else 0; 507 const inner_height = if (btn.height > 2) btn.height - 2 else 0; 508 try out.appendSlice(allocator, "<button type=\"submit\" data-focus-id=\""); 509 var id_buf: [32]u8 = undefined; 510 try out.appendSlice(allocator, try std.fmt.bufPrint(&id_buf, "{d}", .{btn.focus_id})); 511 try out.appendSlice(allocator, "\" style=\"left:"); 512 var pos_buf: [128]u8 = undefined; 513 try out.appendSlice(allocator, try std.fmt.bufPrint(&pos_buf, "{d}ch;top:{d}em;width:{d}ch;height:{d}em", .{ inner_left, inner_top, inner_width, inner_height })); 514 try out.appendSlice(allocator, "\"></button>"); 515 } 516 517 try out.appendSlice(allocator, "</form>"); 518 519 return try out.toOwnedSlice(allocator); 520 } 521 522 fn cellFocusId(focus: *Focus, x: usize, y: usize) ?usize { 523 var iter = focus.children.iterator(); 524 while (iter.next()) |entry| { 525 const child = entry.value_ptr.*; 526 if (!child.focus.focusable) continue; 527 const r = child.rect; 528 if (x >= r.x and y >= r.y and x < r.x + r.size.width and y < r.y + r.size.height) { 529 return entry.key_ptr.*; 530 } 531 } 532 return null; 533 } 534 535 fn appendEscapedHtml(allocator: std.mem.Allocator, out: *std.ArrayList(u8), input: []const u8) !void { 536 for (input) |ch| { 537 switch (ch) { 538 '&' => try out.appendSlice(allocator, "&amp;"), 539 '<' => try out.appendSlice(allocator, "&lt;"), 540 '>' => try out.appendSlice(allocator, "&gt;"), 541 '"' => try out.appendSlice(allocator, "&quot;"), 542 '\'' => try out.appendSlice(allocator, "&#39;"), 543 else => try out.append(allocator, ch), 544 } 545 } 546 } 547 548 // --- helpers -------------------------------------------------------------- 549 550 fn openHaxyMoment( 551 comptime repo_opts: rp.RepoOpts(.xit), 552 repo: *rp.Repo(.xit, repo_opts), 553 ) !rp.Repo(.xit, repo_opts).DB.HashMap(.read_only) { 554 const DB = rp.Repo(.xit, repo_opts).DB; 555 const history = try DB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); 556 const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; 557 const moment = try DB.HashMap(.read_only).init(moment_cursor); 558 const last_object_id_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id")) orelse return error.NotFound; 559 var last_object_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; 560 _ = try last_object_id_cursor.readBytes(&last_object_id); 561 const haxy_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy")) orelse return error.NotFound; 562 const haxy = try DB.ArrayList(.read_only).init(haxy_cursor); 563 const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; 564 const haxy_moments = try DB.HashMap(.read_only).init(haxy_moments_cursor); 565 const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound; 566 return try DB.HashMap(.read_only).init(haxy_moment_cursor); 567 } 568 569 fn getCookieValue(request: *std.http.Server.Request, name: []const u8) ?[]const u8 { 570 var iter = request.iterateHeaders(); 571 while (iter.next()) |header| { 572 if (!std.ascii.eqlIgnoreCase(header.name, "cookie")) continue; 573 var pairs = std.mem.splitScalar(u8, header.value, ';'); 574 while (pairs.next()) |pair| { 575 const trimmed = std.mem.trim(u8, pair, " \t"); 576 const eq = std.mem.indexOfScalar(u8, trimmed, '=') orelse continue; 577 if (std.mem.eql(u8, trimmed[0..eq], name)) { 578 return trimmed[eq + 1 ..]; 579 } 580 } 581 } 582 return null; 583 } 584 585 fn parseFormField(allocator: std.mem.Allocator, body: []const u8, key: []const u8) !?[]u8 { 586 var iter = std.mem.splitScalar(u8, body, '&'); 587 while (iter.next()) |pair| { 588 const eq = std.mem.indexOfScalar(u8, pair, '=') orelse continue; 589 if (std.mem.eql(u8, pair[0..eq], key)) { 590 return try decodeFormValue(allocator, pair[eq + 1 ..]); 591 } 592 } 593 return null; 594 } 595 596 fn decodeFormValue(allocator: std.mem.Allocator, encoded: []const u8) ![]u8 { 597 var out: std.ArrayList(u8) = .empty; 598 errdefer out.deinit(allocator); 599 var i: usize = 0; 600 while (i < encoded.len) : (i += 1) { 601 switch (encoded[i]) { 602 '+' => try out.append(allocator, ' '), 603 '%' => { 604 if (i + 2 >= encoded.len) return error.InvalidPercentEncoding; 605 const hi = std.fmt.charToDigit(encoded[i + 1], 16) catch return error.InvalidPercentEncoding; 606 const lo = std.fmt.charToDigit(encoded[i + 2], 16) catch return error.InvalidPercentEncoding; 607 try out.append(allocator, hi * 16 + lo); 608 i += 2; 609 }, 610 else => try out.append(allocator, encoded[i]), 611 } 612 } 613 return try out.toOwnedSlice(allocator); 614 } 615