╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │2329 } │ │2330 │ │2331 // builds an HTML ` style="color:…;background-color:…"` attribute from a │ │2320 -// cell's fg/bg into `buf`, omitting whichever color is unset. an inverted │ │2332 +// cell's style into `buf`, omitting whichever color is unset. an inverted │ │2333 // cell swaps the two, falling back to the page's --fg/--bg variables for │ │2322 -// whichever side is unset. returns an empty slice when nothing is set. │ │2323 -fn styleAttr(buf: []u8, fg: ?Grid.Color, bg: ?Grid.Color, inverted: bool) []const u8 {│ │2324 - if (fg == null and bg == null and !inverted) return buf[0..0]; │ │2334 +// whichever side is unset. text attributes follow the colors. returns an │ │2335 +// empty slice when nothing is set. │ │2336 +fn styleAttr(buf: []u8, style: Grid.Style) []const u8 { │ │2337 + if (style.eql(.{})) return buf[0..0]; │ │2338 const prefix = " style=\""; │ │2339 @memcpy(buf[0..prefix.len], prefix); │ │2340 var i: usize = prefix.len; │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2328 - i = cssColor(buf, i, "color", if (inverted) bg else fg, if (inverted) "var(--bg)" else null) orelse return buf[0..0]; │ │2329 - i = cssColor(buf, i, "background-color", if (inverted) fg else bg, if (inverted) "var(--fg)" else null) orelse return buf[0..0]; │ │2341 + const inverted = style.inverted; │ │2342 + i = cssColor(buf, i, "color", if (inverted) style.bg else style.fg, if (inverted) "var(--bg)" else null) orelse return buf[0..0]; │ │2343 + i = cssColor(buf, i, "background-color", if (inverted) style.fg else style.bg, if (inverted) "var(--fg)" else null) orelse return buf[0..0];│ │2344 + const decoration: []const u8 = if (style.underline and style.strikethrough) │ │2345 + "text-decoration:underline line-through;" │ │2346 + else if (style.underline) │ │2347 + "text-decoration:underline;" │ │2348 + else if (style.strikethrough) │ │2349 + "text-decoration:line-through;" │ │2350 + else │ │2351 + ""; │ │2352 + const attributes = std.fmt.bufPrint(buf[i..], "{s}{s}{s}{s}", .{ │ │2353 + if (style.bold) "font-weight:bold;" else "", │ │2354 + if (style.italic) "font-style:italic;" else "", │ │2355 + decoration, │ │2356 + if (style.dim) "opacity:.6;" else "", │ │2357 + }) catch return buf[0..0]; │ │2358 + i += attributes.len; │ │2359 + if (i >= buf.len) return buf[0..0]; │ │2360 buf[i] = '"'; │ │2361 return buf[0 .. i + 1]; │ │2362 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2375 return i + s.len; │ │2376 } │ │2377 │ │2378 +// a cell's link as an href, when it's a web or mail url │ │2379 +fn webLink(link: ?[]const u8) ?[]const u8 { │ │2380 + const url = link orelse return null; │ │2381 + for ([_][]const u8{ "http://", "https://", "mailto:" }) |scheme| { │ │2382 + if (std.ascii.startsWithIgnoreCase(url, scheme)) return url; │ │2383 + } │ │2384 + return null; │ │2385 +} │ │2386 + │ │2387 +fn sameLink(a: ?[]const u8, b: ?[]const u8) bool { │ │2388 + if (a) |x| return if (b) |y| std.mem.eql(u8, x, y) else false; │ │2389 + return b == null; │ │2390 +} │ │2391 + │ │2392 fn appendEscapedHtml(allocator: std.mem.Allocator, out: *std.ArrayList(u8), input: []const u8) !void {│ │2393 for (input) |ch| { │ │2394 switch (ch) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ added ─────────╮ │src/markdown.zig│ ╰────────────────╯