╭ Attachment.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const evt = @import("../../event.zig"); 3 const ui = @import("../../ui.zig"); 4 const xit = @import("xit"); 5 const hash = xit.hash; 6 const xitui = xit.xitui; 7 const wgt = xitui.widget; 8 9 pub const WithId = struct { 10 id: [evt.event_id_size * 2]u8, 11 name: []const u8, 12 }; 13 14 // every attachment on a parent event, oldest first. tombstoned ones are 15 // left out. 16 pub fn load( 17 comptime hash_kind: hash.HashKind, 18 arena: *std.heap.ArenaAllocator, 19 haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), 20 parent_id: []const u8, 21 ) ![]const WithId { 22 const DB = evt.EventDB(hash_kind); 23 const index_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, evt.Attachment.parent_id_to_attachment_id_set_key)) orelse return &.{}; 24 const index = try DB.HashMap(.read_only).init(index_cursor); 25 const set_cursor = try index.getCursor(hash.hashInt(hash_kind, parent_id)) orelse return &.{}; 26 const set = try DB.SortedSet(.read_only).init(set_cursor); 27 28 var attachments: std.ArrayList(WithId) = .empty; 29 var iter = try set.iteratorFromIndex(0); 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; 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 attachments: []const WithId, 50 ) !void { 51 const pa = session.page_arena.allocator(); 52 for (attachments) |entry| { 53 var tb = try wgt.TextBox(ui.Widget).init(allocator, entry.name, .{ 54 .border_style = .single, 55 .rounded_corners = true, 56 .wrap_kind = .none, 57 .label = " attachment ", 58 }); 59 errdefer tb.deinit(allocator); 60 tb.getFocus().focusable = true; 61 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 }); 63 } 64 } 65 66 // the url an attachment's bytes are served from 67 pub fn url(page_arena: *std.heap.ArenaAllocator, identity: []const u8, id: []const u8) ![]const u8 { 68 return if (identity.len == 0) 69 try std.fmt.allocPrint(page_arena.allocator(), "/attachment:{s}", .{id}) 70 else 71 try std.fmt.allocPrint(page_arena.allocator(), "/repo/{s}/attachment:{s}", .{ identity, id }); 72 } 73