╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │449 }); │ │450 } │ │451 │ │449 -// create an issue in the repo the form's page names and redirect to it. │ │450 -fn handleIssueNew( │ │452 +// create an issue or discussion in the repo the form's page names and redirect to it.│ │453 +fn handleTopicNew( │ │454 io: std.Io, │ │455 request: *std.http.Server.Request, │ │456 allocator: std.mem.Allocator, │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │457 base: []const u8, │ │458 host: Host, │ │459 + kind: evt.EventKind, │ │460 ) !void { │ │461 var author_arena = std.heap.ArenaAllocator.init(allocator);│ │462 defer author_arena.deinit(); │ ╰────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │478 const description = try std.mem.replaceOwned(u8, allocator, description_crlf, "\r\n", "\n"); │ │479 defer allocator.free(description); │ │480 │ │477 - if (!evt.Issue.fieldsValid(title, tags)) { │ │478 - const form_location = try std.fmt.allocPrint(allocator, "{s}/issues/new", .{base}); │ │481 + const valid = switch (kind) { │ │482 + .issue => evt.Issue.fieldsValid(title, tags), │ │483 + .discussion => evt.Discussion.fieldsValid(title, tags), │ │484 + else => unreachable, │ │485 + }; │ │486 + if (!valid) { │ │487 + const form_location = try std.fmt.allocPrint(allocator, "{s}/{s}s/new", .{ base, @tagName(kind) });│ │488 defer allocator.free(form_location); │ │489 try request.respond("", .{ │ │490 .status = .see_other, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │501 .id = event_id_hex, │ │502 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │503 .author = author, │ │495 - .event = .{ .issue = .{ │ │496 - .title = title, │ │497 - .description = description, │ │498 - .tags = tags, │ │499 - } }, │ │504 + .event = switch (kind) { │ │505 + .issue => .{ .issue = .{ .title = title, .description = description, .tags = tags } }, │ │506 + .discussion => .{ .discussion = .{ .title = title, .description = description, .tags = tags } },│ │507 + else => unreachable, │ │508 + }, │ │509 }; │ │510 │ │511 const not_found = "repo not found"; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │559 }, │ │560 } │ │561 │ │553 - const location = try std.fmt.allocPrint(allocator, "{s}/issue:{s}", .{ base, &event_id_hex }); │ │562 + const location = try std.fmt.allocPrint(allocator, "{s}/{s}:{s}", .{ base, @tagName(kind), &event_id_hex });│ │563 defer allocator.free(location); │ │564 try request.respond("", .{ │ │565 .status = .see_other, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │569 │ │570 const CommentBaseParts = struct { │ │571 repo_base: []const u8, │ │572 + thread_kind: evt.EventKind, │ │573 thread_id: [evt.event_id_size]u8, │ │574 comment_id: ?[evt.event_id_size]u8,│ │575 }; │ ╰────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │576 │ │577 fn commentBaseParts(base: []const u8) ?CommentBaseParts { │ │568 - const issue_infix = "/issue:"; │ │569 - const comment_infix = "/comment:"; │ │570 - const id_len = evt.event_id_size * 2; │ │571 - const issue_at = std.mem.lastIndexOf(u8, base, issue_infix) orelse return null; │ │572 - const tail = base[issue_at + issue_infix.len ..]; │ │573 - if (tail.len < id_len) return null; │ │574 - │ │578 + const route = ui.RoutablePage.fromUrl(base) orelse ui.RoutablePage.fromUrlLocal(base) orelse return null; │ │579 + const identity, const thread_kind, const thread_hex, const comment_hex = switch (route) { │ │580 + .repo_issues => |*issue| .{ issue.name.slice(), evt.EventKind.issue, issue.selected.slice(), issue.comment.slice() }, │ │581 + .repo_discussions => |*discussion| .{ discussion.name.slice(), evt.EventKind.discussion, discussion.selected.slice(), discussion.comment.slice() },│ │582 + else => return null, │ │583 + }; │ │584 var thread_id: [evt.event_id_size]u8 = undefined; │ │576 - _ = std.fmt.hexToBytes(&thread_id, tail[0..id_len]) catch return null; │ │577 - var comment_id: ?[evt.event_id_size]u8 = null; │ │578 - if (tail.len != id_len) { │ │579 - if (tail.len != id_len + comment_infix.len + id_len) return null; │ │580 - if (!std.mem.eql(u8, tail[id_len .. id_len + comment_infix.len], comment_infix)) return null; │ │585 + _ = std.fmt.hexToBytes(&thread_id, thread_hex) catch return null; │ │586 + const comment_id = if (comment_hex.len == 0) null else blk: { │ │587 var id: [evt.event_id_size]u8 = undefined; │ │582 - _ = std.fmt.hexToBytes(&id, tail[id_len + comment_infix.len ..]) catch return null; │ │583 - comment_id = id; │ │584 - } │ │585 - return .{ .repo_base = base[0..issue_at], .thread_id = thread_id, .comment_id = comment_id }; │ │588 + _ = std.fmt.hexToBytes(&id, comment_hex) catch return null; │ │589 + break :blk id; │ │590 + }; │ │591 + const repo_base_len = if (identity.len == 0) 0 else "/repo/".len + identity.len; │ │592 + return .{ .repo_base = base[0..repo_base_len], .thread_kind = thread_kind, .thread_id = thread_id, .comment_id = comment_id }; │ │593 } │ │594 │ │588 -// create a reply from an issue or comment /new form, then redirect to its │ │589 -// permalink. the rest of the url identifies the thread and parent. │ │595 +// create a reply and redirect to its permalink. │ │596 fn handleCommentNew( │ │597 io: std.Io, │ │598 request: *std.http.Server.Request, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │684 }, │ │685 } │ │686 │ │681 - const location = try std.fmt.allocPrint(allocator, "{s}/issue:{s}/comment:{s}", .{ parts.repo_base, &thread_id_hex, &event_id_hex }); │ │687 + const location = try std.fmt.allocPrint(allocator, "{s}/{s}:{s}/comment:{s}", .{ parts.repo_base, @tagName(parts.thread_kind), &thread_id_hex, &event_id_hex });│ │688 defer allocator.free(location); │ │689 try request.respond("", .{ │ │690 .status = .see_other, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │ 995 } │ │ 996 } │ │ 997 │ │ 992 -// edit actions share a suffix; the base identifies whether the form names an│ │ 993 -// issue or comment. │ │ 998 +// edit actions share a suffix; the base identifies the event being edited. │ │ 999 fn handleEdit( │ │1000 io: std.Io, │ │1001 request: *std.http.Server.Request, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1003 base: []const u8, │ │1004 host: Host, │ │1005 ) !void { │ │1001 - if (commentBaseParts(base)) |parts| { │ │1002 - if (parts.comment_id != null) return handleCommentEdit(io, request, allocator, base, host, parts);│ │1003 - } │ │1004 - return handleIssueEdit(io, request, allocator, base, host); │ │1006 + const parts = commentBaseParts(base) orelse return respondRemoveNotFound(request); │ │1007 + if (parts.comment_id != null) return handleCommentEdit(io, request, allocator, base, host, parts); │ │1008 + return handleTopicEdit(io, request, allocator, base, host, parts); │ │1009 } │ │1010 │ │1011 // replace the body of the comment the url names, then redirect to its │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯