╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Discussions.zig ──────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
138 else if (repo_kind == .git) 139 return empty 140 else 141 evt.currentMoment(repo_opts, repo)) catch { 142 if (strict) return error.NotFound; 143 return empty; 144 }; 145 146 const set_maybe: ?DB.SortedSet(.read_only) = if (labeled) blk: { 147 const labels_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.label_to_id_set_key)) orelse return error.NotFound; 148 const label_sets = try DB.SortedMap(.read_only).init(labels_cursor); 149 const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, empty.label)); 150 const cursor = try label_sets.getCursor(decoded) orelse return error.NotFound; 151 break :blk try DB.SortedSet(.read_only).init(cursor); 152 } else if (try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.active_id_set_key))) |cursor| 153 try DB.SortedSet(.read_only).init(cursor) 154 else 155 null; 156 157 const records_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.record_map_key)) orelse { 158 if (strict) return error.NotFound; 159 return empty; 160 }; 161 const records = try DB.HashMap(.read_only).init(records_cursor); 162 163 var root_key: ?[]const u8 = null; 164 if (rooted) { 165 const id = evt.parseEventId(empty.selected_id) catch return error.NotFound; 166 const record_cursor = try records.getCursor(hash.hashInt(repo_opts.hash, &id)) orelse return error.NotFound; 167 const record = try evt.read(evt.Discussion.Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(record_cursor)); 168 if (record.removed) return error.NotFound; 169 const activity_order = try evt.Discussion.activityOrder(DB, repo_opts.hash, haxy_moment, &id); 170 const order_key = try aa.dupe(u8, &evt.orderKeyDesc(activity_order, &id)); 171 const set = set_maybe orelse return error.NotFound; 172 if (!try set.contains(order_key)) return error.NotFound; 173 root_key = order_key; 174 } 175 176 const discussion_comments_start = if (empty.comment_id.len == 0) comments_start else 0; 177 const loaded_window = try thread.loadWindow(Self, .discuss, repo_opts.hash, arena, admin_moment, haxy_moment, records, set_maybe, root_key, null, empty.selected_id, discussion_comments_start, empty.search, viewer); 178 const comment_page = if (empty.comment_id.len == 0) 179 null 180 else 181 try Comment.init(repo_opts.hash, arena, admin_moment, haxy_moment, empty.selected_id, empty.comment_id, comments_start, viewer); 182 183 var label_names: std.ArrayList([]const u8) = .empty; 184 if (try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.label_to_id_set_key))) |labels_cursor| { 185 const label_sets = try DB.SortedMap(.read_only).init(labels_cursor); 186 var label_iter = try label_sets.iterator(); 187 while (try label_iter.next()) |entry_cursor| { 188 if (label_names.items.len == max_labels) break; 189 var entry = entry_cursor; 190 const pair = try entry.readKeyValuePair(); 191 try label_names.append(aa, try pair.key_cursor.readBytesAlloc(aa, null)); 192 } 193 } 194 195 return .{ 196 .identity = empty.identity, 197 .label = empty.label, 198 .search = empty.search, 199 .selected_id = empty.selected_id, 200 .comment_id = empty.comment_id, 201 .comments_start = comments_start, 202 .comment_page = comment_page, 203 .viewer = viewer, 204 .recent = loaded_window, 205 .view = empty.view, 206 .description_page = empty.description_page, 207 .labels = label_names.items, 208 }; 209 } 210 211 pub const View = thread.View(.discuss, Self); 212 pub const Detail = thread.Detail(.discuss, Self); 213 pub const detail_widget_name = "repo_discussion_detail"; 214 215 pub const Header = thread.Header; 216 217 const recent_tab_label = "recent"; 218 const labels_tab_label = "labels"; 219 const edit_tab_label = "edit"; 220 const reply_tab_label = "reply"; 221 const remove_tab_label = "remove"; 222 const new_tab_label = "new"; 223 224 // tabs switching between the discussions page's views 225 pub fn initHeader(allocator: std.mem.Allocator, session: *ui.Session, data: *const Self) !Header { 226 var header = try Header.init(allocator, session, data.search); 227 errdefer header.deinit(allocator); 228 const selected_index = View.viewIndex(data.view); 229 const page_selected = std.meta.activeTag(session.data.current_page) == .repo_discussions; 230 231 // recent discussions 232 { 233 const route = try thread.searchRoute(ui.RoutablePage.repoDiscussionsRoute(data.identity, data.label, ""), data.search); 234 const link = try ui.inPageTabLink(session, route, page_selected and selected_index == 0); 235 var label_buf: [64]u8 = undefined; 236 const label = try thread.countLabel(&label_buf, recent_tab_label, data.recent); 237 try header.addTab(allocator, label, link, 0); 238 } 239 240 // labels tab 241 { 242 const labels_route = try thread.searchRoute(ui.RoutablePage.repoThreadLabelsRoute(.discuss, data.identity, data.label), data.search); 243 const labels_link = try ui.inPageTabLink(session, labels_route, page_selected and selected_index == View.viewIndex(.labels)); 244 try header.addTab(allocator, labels_tab_label, labels_link, View.viewIndex(.labels)); 245 } 246 247 // new-discussion tab; an edit or comment url shows its tab in this place 248 { 249 const route = switch (data.view) { 250 .edit => ui.RoutablePage.repoThreadEditRoute(.discuss, data.identity, data.selected_id) orelse return error.RouteTooLong, 251 .new_comment => ui.RoutablePage.repoThreadCommentNewRoute(.discuss, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, 252 .edit_comment => ui.RoutablePage.repoThreadCommentEditRoute(.discuss, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, 253 .remove => ui.RoutablePage.repoThreadRemoveRoute(.discuss, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, 254 else => ui.RoutablePage.repoThreadNewRoute(.discuss, data.identity) orelse return error.RouteTooLong, 255 }; 256 const link = try ui.inPageTabLink(session, route, page_selected and selected_index == View.viewIndex(.new)); 257 const label: []const u8 = switch (data.view) { 258 .edit => edit_tab_label, 259 .new_comment => reply_tab_label, 260 .edit_comment => edit_tab_label, 261 .remove => remove_tab_label, 262 else => new_tab_label, 263 }; 264 try header.addTab(allocator, label, link, View.viewIndex(.new)); 265 } 266 267 header.select(View.viewIndex(data.view)); 268 return header; 269 } 270