Forge
csharp3407750f
1using System.Net;
2using System.Text;
3
4namespace AgentForge.Plugin.View;
5
6/// <summary>Composable, escaped HTML fragments for server-rendered view pages.</summary>
7public static partial class ForgeHtml
8{
9 public static string H(string? value) => WebUtility.HtmlEncode(value ?? "");
10
11 public static string Fragment(params ReadOnlySpan<string> parts)
12 {
13 if (parts.Length == 0)
14 return "";
15 if (parts.Length == 1)
16 return parts[0];
17 var sb = new StringBuilder();
18 foreach (var part in parts)
19 sb.Append(part);
20 return sb.ToString();
21 }
22
23 public static void Write(StringBuilder target, string html) => target.Append(html);
24
25 public static void Write(StringBuilder target, params ReadOnlySpan<string> parts)
26 {
27 foreach (var part in parts)
28 target.Append(part);
29 }
30
31 public static string Raw(string html) => html;
32
33 public static string Text(string? text) => H(text);
34
35 public static string Tag(string name, string? cssClass, ReadOnlySpan<string> inner)
36 {
37 var sb = new StringBuilder();
38 sb.Append('<').Append(name);
39 if (!string.IsNullOrEmpty(cssClass))
40 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
41 sb.Append('>');
42 foreach (var part in inner)
43 sb.Append(part);
44 sb.Append("</").Append(name).Append('>');
45 return sb.ToString();
46 }
47
48 public static string Tag(string name, string? cssClass, string inner) =>
49 Tag(name, cssClass, [inner]);
50
51 public readonly record struct ForgeHtmlAttr(string Name, string? Value = null);
52
53 public static string Tag(
54 string name,
55 string? cssClass,
56 ReadOnlySpan<ForgeHtmlAttr> attrs,
57 ReadOnlySpan<string> inner)
58 {
59 var sb = new StringBuilder();
60 sb.Append('<').Append(name);
61 if (!string.IsNullOrEmpty(cssClass))
62 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
63 foreach (var attr in attrs)
64 {
65 if (attr.Value is null)
66 sb.Append(' ').Append(attr.Name);
67 else
68 sb.Append(' ').Append(attr.Name).Append("=\"").Append(H(attr.Value)).Append('"');
69 }
70
71 sb.Append('>');
72 foreach (var part in inner)
73 sb.Append(part);
74 sb.Append("</").Append(name).Append('>');
75 return sb.ToString();
76 }
77
78 public static string Div(string? cssClass, string inner) => Tag("div", cssClass, inner);
79
80 public static string P(string? cssClass, string inner) => Tag("p", cssClass, inner);
81
82 public static string Li(string? cssClass, string inner) => Tag("li", cssClass, inner);
83
84 public static string Ul(string cssClass, string inner) => Tag("ul", cssClass, inner);
85
86 public static string H1(string text) => Tag("h1", null, Text(text));
87
88 public static string H1(params ReadOnlySpan<string> inner) => Tag("h1", null, inner);
89
90 public static string H2(string text, string? cssClass = null) => Tag("h2", cssClass, Text(text));
91
92 public static string H3(string text, string? cssClass = null) => Tag("h3", cssClass, Text(text));
93
94 public static string Header(string? cssClass, params ReadOnlySpan<string> inner) =>
95 Tag("header", cssClass, inner);
96
97 public static string Aside(string? cssClass, params ReadOnlySpan<string> inner) =>
98 Tag("aside", cssClass, inner);
99
100 public static string P(string? cssClass, params ReadOnlySpan<string> inner) =>
101 Tag("p", cssClass, inner);
102
103 public static string Div(string? cssClass, params ReadOnlySpan<string> inner) =>
104 Tag("div", cssClass, inner);
105
106 public static string Span(string? cssClass, params ReadOnlySpan<string> inner) =>
107 Tag("span", cssClass, inner);
108
109 public static string SpanData(string? cssClass, string? dataForgeUri, params ReadOnlySpan<string> inner)
110 {
111 var sb = new StringBuilder("<span");
112 if (!string.IsNullOrEmpty(cssClass))
113 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
114 if (!string.IsNullOrEmpty(dataForgeUri))
115 sb.Append(" data-forge-uri=\"").Append(H(dataForgeUri)).Append('"');
116 sb.Append('>');
117 foreach (var part in inner)
118 sb.Append(part);
119 sb.Append("</span>");
120 return sb.ToString();
121 }
122
123 public static string Strong(string text) => Tag("strong", null, Text(text));
124
125 public static string Strong(string? cssClass, params ReadOnlySpan<string> inner) =>
126 Tag("strong", cssClass, inner);
127
128 public static string Em(string text) => Tag("em", null, Text(text));
129
130 public static string Code(string text) => Tag("code", null, Text(text));
131
132 public static string Badge(string text) => Span("badge", Text(text));
133
134 public static string Mark(string cssClass, string symbol, bool ariaHidden = true)
135 {
136 var sb = new StringBuilder("<span class=\"").Append(H(cssClass)).Append('"');
137 if (ariaHidden)
138 sb.Append(" aria-hidden=\"true\"");
139 sb.Append('>').Append(H(symbol)).Append("</span>");
140 return sb.ToString();
141 }
142
143 public static string A(string href, string label, string? cssClass = null)
144 {
145 var sb = new StringBuilder();
146 sb.Append("<a href=\"").Append(H(href)).Append('"');
147 if (!string.IsNullOrEmpty(cssClass))
148 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
149 sb.Append('>').Append(H(label)).Append("</a>");
150 return sb.ToString();
151 }
152
153 public static string AExternal(string href, string label, string? cssClass = null)
154 {
155 var sb = new StringBuilder();
156 sb.Append("<a href=\"").Append(H(href)).Append('"');
157 sb.Append(" target=\"_blank\" rel=\"noopener noreferrer\"");
158 if (!string.IsNullOrEmpty(cssClass))
159 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
160 sb.Append('>').Append(H(label)).Append("</a>");
161 return sb.ToString();
162 }
163
164 public static string ARaw(string href, params ReadOnlySpan<string> inner)
165 {
166 var sb = new StringBuilder();
167 sb.Append("<a href=\"").Append(H(href)).Append("\">");
168 foreach (var part in inner)
169 sb.Append(part);
170 sb.Append("</a>");
171 return sb.ToString();
172 }
173
174 public static string Li(string? cssClass, params ReadOnlySpan<string> inner) =>
175 Tag("li", cssClass, inner);
176
177 public static string Ul(string cssClass, params ReadOnlySpan<string> items) =>
178 Tag("ul", cssClass, items);
179
180 public static string Nav(string cssClass, string ariaLabel, params ReadOnlySpan<string> inner)
181 {
182 var sb = new StringBuilder("<nav class=\"").Append(H(cssClass)).Append("\" aria-label=\"").Append(H(ariaLabel)).Append("\">");
183 foreach (var part in inner)
184 sb.Append(part);
185 sb.Append("</nav>");
186 return sb.ToString();
187 }
188
189 public static string Table(string? cssClass, params ReadOnlySpan<string> inner) =>
190 Tag("table", cssClass, inner);
191
192 public static string Thead(params ReadOnlySpan<string> inner) => Tag("thead", null, inner);
193
194 public static string Tbody(params ReadOnlySpan<string> inner) => Tag("tbody", null, inner);
195
196 public static string Tr(params ReadOnlySpan<string> inner) => Tag("tr", null, inner);
197
198 public static string Tr(string? cssClass, string? id, params ReadOnlySpan<string> inner)
199 {
200 var sb = new StringBuilder("<tr");
201 if (!string.IsNullOrEmpty(cssClass))
202 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
203 if (!string.IsNullOrEmpty(id))
204 sb.Append(" id=\"").Append(H(id)).Append('"');
205 sb.Append('>');
206 foreach (var part in inner)
207 sb.Append(part);
208 sb.Append("</tr>");
209 return sb.ToString();
210 }
211
212 public static string Td(string? cssClass, params ReadOnlySpan<string> inner)
213 {
214 var sb = new StringBuilder("<td");
215 if (!string.IsNullOrEmpty(cssClass))
216 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
217 sb.Append('>');
218 foreach (var part in inner)
219 sb.Append(part);
220 sb.Append("</td>");
221 return sb.ToString();
222 }
223
224 public static string TdColspan(int colspan, string? cssClass, params ReadOnlySpan<string> inner)
225 {
226 var sb = new StringBuilder("<td colspan=\"").Append(colspan).Append('"');
227 if (!string.IsNullOrEmpty(cssClass))
228 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
229 sb.Append('>');
230 foreach (var part in inner)
231 sb.Append(part);
232 sb.Append("</td>");
233 return sb.ToString();
234 }
235
236 public static string Th(string text) => Tag("th", null, Text(text));
237
238 public static string Td(params ReadOnlySpan<string> inner) => Tag("td", null, inner);
239
240 public static string Td(string inner) => Tag("td", null, inner);
241
242 public static string Code(string text, string? cssClass, string? title)
243 {
244 var sb = new StringBuilder("<code");
245 if (!string.IsNullOrEmpty(cssClass))
246 sb.Append(" class=\"").Append(H(cssClass)).Append('"');
247 if (!string.IsNullOrEmpty(title))
248 sb.Append(" title=\"").Append(H(title)).Append('"');
249 sb.Append('>').Append(H(text)).Append("</code>");
250 return sb.ToString();
251 }
252
253 public static string Article(string? cssClass, params ReadOnlySpan<string> inner) =>
254 Tag("article", cssClass, inner);
255
256 public static string Option(string value, string label, bool selected)
257 {
258 var sb = new StringBuilder();
259 sb.Append("<option value=\"").Append(H(value)).Append('"');
260 if (selected)
261 sb.Append(" selected");
262 sb.Append('>').Append(H(label)).Append("</option>");
263 return sb.ToString();
264 }
265}
266
View only · write via MCP/CIDE