╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Attachment.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
33 if (record.removed) continue; 34 try attachments.append(arena.allocator(), .{ 35 .id = std.fmt.bytesToHex(id_bytes, .lower), 36 .name = record.name, 37 }); 38 } 39 return attachments.items; 40 } 41 42 // one download link per attachment. the url isn't a page route, so the link 43 // carries the raw-link prefix rather than the cross-page one. 44 pub fn appendRows( 45 allocator: std.mem.Allocator, 46 box: *wgt.Box(ui.Widget), 47 session: *ui.Session, 48 identity: []const u8, 49 target_url: []const u8, 50 attachments: []const WithId, 51 ) !void { 52 const pa = session.page_arena.allocator(); 53 for (attachments) |entry| { 54 var row = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .horiz }); 55 errdefer row.deinit(allocator); 56 57 var tb = try wgt.TextBox.init(allocator, entry.name, .{ 58 .border = .single, 59 .round_corners = true, 60 .wrap_kind = .none, 61 .top_label = .{ .text = " attachment " }, 62 }); 63 errdefer tb.deinit(allocator); 64 tb.getFocus().mode = .all; 65 tb.getFocus().kind = .{ .custom = try pa.print("{s}{s}", .{ ui.raw_link_prefix, try url(session.page_arena, identity, &entry.id) }) }; 66 try row.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); 67 68 // the ✕ opens a page confirming the removal 69 var remove = try wgt.TextBox.init(allocator, "✕", .{ .border = .single, .round_corners = true, .wrap_kind = .none }); 70 errdefer remove.deinit(allocator); 71 remove.getFocus().mode = .all; 72 remove.getFocus().kind = .{ .custom = try pa.print("a:{s}/attachment:{s}/remove", .{ target_url, &entry.id }) }; 73 try row.children.put(allocator, remove.getFocus().id, .{ .widget = .{ .text_box = remove }, .rect = null, .min_size = .{ .width = 3, .height = null } }); 74 75 row.getFocus().child_id = row.children.keys()[0]; 76 try box.children.put(allocator, row.getFocus().id, .{ .widget = .{ .box = row }, .rect = null, .min_size = null }); 77 } 78 } 79 80 // whether a row is an attachment's, by the remove link on its ✕ 81 pub fn isRow(row: *wgt.Box(ui.Widget)) bool { 82 const last = row.children.values()[row.children.count() - 1].widget; 83 if (last != .text_box) return false; 84 const link = switch (last.text_box.focus.kind) { 85 .custom => |custom| custom, 86 else => return false, 87 }; 88 return std.mem.startsWith(u8, link, "a:") and std.mem.indexOf(u8, link, "/attachment:") != null and std.mem.endsWith(u8, link, "/remove"); 89 } 90 91 // the url an attachment's bytes are served from 92 pub fn url(page_arena: *std.heap.ArenaAllocator, identity: []const u8, id: []const u8) ![]const u8 { 93 return if (identity.len == 0) 94 try page_arena.allocator().print("/attachment:{s}", .{id}) 95 else 96 try page_arena.allocator().print("/{s}/attachment:{s}", .{ identity, id }); 97 } 98