╭ changed ─────────────────╮ │src/ui/Repo/Attachment.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │11 name: []const u8, │ │12 }; │ │13 │ │14 -// every attachment on a parent event, oldest first. tombstoned ones are│ │14 +// every attachment on a parent event, oldest first. removed ones are │ │15 // left out. │ │16 pub fn load( │ │17 comptime hash_kind: hash.HashKind, │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │30 while (try iter.next()) |cursor_val| { │ │31 const id_bytes = try evt.readOrderKeyId(DB, cursor_val); │ │32 const record = (try evt.Attachment.readById(DB, hash_kind, haxy_moment, arena, &id_bytes)) orelse continue;│ │33 - if (record.deleted) continue; │ │33 + if (record.removed) continue; │ │34 try attachments.append(arena.allocator(), .{ │ │35 .id = std.fmt.bytesToHex(id_bytes, .lower), │ │36 .name = record.name, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────╮ │46 box: *wgt.Box(ui.Widget), │ │47 session: *ui.Session, │ │48 identity: []const u8, │ │49 + parent_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_style = null, .direction = .horiz });│ │55 + errdefer row.deinit(allocator); │ │56 + │ │57 var tb = try wgt.TextBox(ui.Widget).init(allocator, entry.name, .{ │ │58 .border_style = .single, │ │59 .rounded_corners = true, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 63 errdefer tb.deinit(allocator); │ │ 64 tb.getFocus().focusable = true; │ │ 65 tb.getFocus().kind = .{ .custom = try std.fmt.allocPrint(pa, "{s}{s}", .{ ui.raw_link_prefix, try url(session.page_arena, identity, &entry.id) }) }; │ │ 62 - try box.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │ 66 + try row.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │ 67 + │ │ 68 + if (session.data.is_local or session.data.user_id != null) { │ │ 69 + row.getFocus().kind = .{ .custom = try std.fmt.allocPrint(pa, "form:{s}/attachment:{s}/remove", .{ parent_url, &entry.id }) }; │ │ 70 + var remove = try wgt.TextBox(ui.Widget).init(allocator, "✕", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │ 71 + errdefer remove.deinit(allocator); │ │ 72 + remove.getFocus().focusable = true; │ │ 73 + remove.getFocus().kind = .{ .custom = "submit" }; │ │ 74 + try row.children.put(allocator, remove.getFocus().id, .{ .widget = .{ .text_box = remove }, .rect = null, .min_size = .{ .width = 3, .height = null } });│ │ 75 + } │ │ 76 + │ │ 77 + row.getFocus().child_id = row.children.keys()[0]; │ │ 78 + try box.children.put(allocator, row.getFocus().id, .{ .widget = .{ .box = row }, .rect = null, .min_size = null }); │ │ 79 } │ │ 80 +} │ │ 81 + │ │ 82 +// the attachment id carried by a removable row │ │ 83 +pub fn removeId(row: *wgt.Box(ui.Widget)) ?[evt.event_id_size]u8 { │ │ 84 + const prefix = "form:"; │ │ 85 + const infix = "/attachment:"; │ │ 86 + const suffix = "/remove"; │ │ 87 + const action = switch (row.getFocus().kind) { │ │ 88 + .custom => |custom| custom, │ │ 89 + else => return null, │ │ 90 + }; │ │ 91 + if (!std.mem.startsWith(u8, action, prefix) or !std.mem.endsWith(u8, action, suffix)) return null; │ │ 92 + const at = std.mem.lastIndexOf(u8, action, infix) orelse return null; │ │ 93 + const id = action[at + infix.len .. action.len - suffix.len]; │ │ 94 + if (id.len != evt.event_id_size * 2) return null; │ │ 95 + var bytes: [evt.event_id_size]u8 = undefined; │ │ 96 + _ = std.fmt.hexToBytes(&bytes, id) catch return null; │ │ 97 + return bytes; │ │ 98 } │ │ 99 │ │100 // the url an attachment's bytes are served from │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯