Markdown
tier 1 — the frame
Two independent pieces: .markdown-body styles rendered Markdown wherever it
appears, and .md-editor is a toolbar + textarea + live preview driven by
drSimpleUi.md.
.markdown-body
Put it on the container holding rendered HTML. It normalises headings, lists, code, quotes and links onto the tokens, and strips the first and last margin so it sits flush in a card body.
Escalation policy
Raise to P2 when either condition holds:
- More than 20 users are affected
- A
VIPflag is set on the caller
Never raise priority and reroute in the same decision — the receiving group loses the audit trail.
if (affected > 20 || caller.IsVip)
priority = Priority.P2;
See the routing matrix for target groups.
<div class="card">
<div class="card-body">
<div class="markdown-body">
<h2>Escalation policy</h2>
<p>Raise to <strong>P2</strong> when <em>either</em> condition holds:</p>
<ul>
<li>More than 20 users are affected</li>
<li>A <code>VIP</code> flag is set on the caller</li>
</ul>
<blockquote>Never raise priority and reroute in the same decision — the receiving group loses the audit trail.</blockquote>
<pre><code>if (affected > 20 || caller.IsVip)
priority = Priority.P2;</code></pre>
<p>See the <a href="#">routing matrix</a> for target groups.</p>
</div>
</div>
</div>
Tables and nested lists
A table in rendered Markdown gets .table's own borders and
header treatment without the class, so a release note or a knowledge-base article looks like
the rest of the app. Nested lists indent one step per level and keep their own markers.
The rendered container is the one place in an app where the HTML comes from somewhere else, so
it must survive anything: a long <code> breaks inside a word rather than
running past its column, and a wide table scrolls rather than widening the page.
Release 0.5.0
Adds the live-state family. Nothing was renamed, so an upgrade needs no app changes.
| Class | Layer | What it is for |
|---|---|---|
.health-badge | dr.paint | A connection's state, as a dot and a word |
.output | dr.paint | Lines of machine output, with follow-tail |
.stale | dr.paint | How old the figure beside it is |
Before bumping:
- Grep your own stylesheets for the names above.
- Check each one you already style — both rule sets will apply.
- Yours wins, because your stylesheet is unlayered.
- The library's declarations you have not set still land.
- Rename yours, or delete it if the library's does the job.
<div class="markdown-body">
<h2>Release 0.5.0</h2>
<p>Adds the live-state family. Nothing was renamed, so an upgrade needs no app changes.</p>
<table>
<thead>
<tr><th>Class</th><th>Layer</th><th>What it is for</th></tr>
</thead>
<tbody>
<tr><td><code>.health-badge</code></td><td>dr.paint</td><td>A connection's state, as a dot and a word</td></tr>
<tr><td><code>.output</code></td><td>dr.paint</td><td>Lines of machine output, with follow-tail</td></tr>
<tr><td><code>.stale</code></td><td>dr.paint</td><td>How old the figure beside it is</td></tr>
</tbody>
</table>
<p>Before bumping:</p>
<ol>
<li>Grep your own stylesheets for the names above.</li>
<li>Check each one you already style — both rule sets will apply.
<ul>
<li>Yours wins, because your stylesheet is unlayered.</li>
<li>The library's declarations you have not set still land.</li>
</ul>
</li>
<li>Rename yours, or delete it if the library's does the job.</li>
</ol>
</div>
.md-editor
Live below — type in it, use the toolbar, then switch to Preview. The
wiring: the root carries data-md-view, the textarea
data-md-input, the preview pane data-md-preview, and toolbar
buttons data-md-cmd.
The markup alone does nothing. The toolbar, the preview and the
Write/Preview switch are behaviour, so an editor renders perfectly and sits inert until
something calls drSimpleUi.md.init() — from C#,
IDrSimpleUi.InitMarkdownAsync() in OnAfterRenderAsync, which is
what this page does. It takes no element and wires every editor in the document, and it is
idempotent per editor, so calling it again after a re-render only picks up new ones.
The Write/Preview switch is the ordinary
.segmented control, with
data-md-tab on each radio — it is exactly what a segmented control is for, so
the editor gets the radio group's keyboard behaviour and announcement instead of two
buttons with a toggled class. init() assigns the shared name,
because only it knows how many editors are on the page.
<div class="md-editor" data-md-view="write" id="demo-md">
<div class="md-toolbar">
<button class="md-tool" type="button" data-md-cmd="bold" aria-label="Bold" data-tip="Wrap the selection in ** **."><i class="ri-bold"></i></button>
<button class="md-tool" type="button" data-md-cmd="italic" aria-label="Italic"><i class="ri-italic"></i></button>
<button class="md-tool" type="button" data-md-cmd="code" aria-label="Code"><i class="ri-code-line"></i></button>
<button class="md-tool" type="button" data-md-cmd="h2" aria-label="Heading"><i class="ri-h-2"></i></button>
<button class="md-tool" type="button" data-md-cmd="ul" aria-label="Bullet list"><i class="ri-list-unordered"></i></button>
<button class="md-tool" type="button" data-md-cmd="ol" aria-label="Numbered list"><i class="ri-list-ordered"></i></button>
<button class="md-tool" type="button" data-md-cmd="quote" aria-label="Quote"><i class="ri-quote-text"></i></button>
<button class="md-tool" type="button" data-md-cmd="link" aria-label="Link"><i class="ri-link"></i></button>
<div class="segmented" role="group" aria-label="Editor view">
<label class="segmented-option">
<input type="radio" data-md-tab="write" checked /> Write</label>
<label class="segmented-option">
<input type="radio" data-md-tab="preview" /> Preview</label>
</div>
</div>
<textarea class="form-input md-input" data-md-input rows="8" aria-label="Markdown source">## Escalation policy
Raise to **P2** when _either_ condition holds:
- More than 20 users are affected
- A `VIP` flag is set on the caller
> Never raise priority and reroute in the same decision.
</textarea>
<div class="markdown-body md-preview" data-md-preview></div>
</div>
As a form field
Live below. The editor in place: a .form-label pointing at the
textarea's own id, a .form-hint reached by aria-describedby, and the
.form-actions row. Label the textarea, not the
.md-editor wrapper — the textarea is the control.
A shorter toolbar is a choice, not a variant: leave out the data-md-cmd buttons
the field does not need and the rest keeps working.
Kept on the order and shown in its history. Markdown is rendered.
<div class="form-field" style="max-width:560px">
<label class="form-label" for="md-reason">Reason for the decision</label>
<div class="md-editor" data-md-view="write" id="reason-md">
<div class="md-toolbar">
<button class="md-tool" type="button" data-md-cmd="bold" aria-label="Bold"><i class="ri-bold"></i></button>
<button class="md-tool" type="button" data-md-cmd="code" aria-label="Code"><i class="ri-code-line"></i></button>
<button class="md-tool" type="button" data-md-cmd="ul" aria-label="Bullet list"><i class="ri-list-unordered"></i></button>
<button class="md-tool" type="button" data-md-cmd="link" aria-label="Link"><i class="ri-link"></i></button>
<div class="segmented" role="group" aria-label="Editor view">
<label class="segmented-option">
<input type="radio" data-md-tab="write" checked /> Write</label>
<label class="segmented-option">
<input type="radio" data-md-tab="preview" /> Preview</label>
</div>
</div>
<textarea class="form-input md-input" id="md-reason" data-md-input rows="5"
aria-describedby="md-reason-hint">Dispatching the three reserved lines now.
- Lines 4 and 5 are out of stock until Monday
- The customer accepted a split delivery
</textarea>
<div class="markdown-body md-preview" data-md-preview></div>
</div>
<p class="form-hint" id="md-reason-hint">Kept on the order and shown in its history. Markdown is rendered.</p>
<div class="form-actions">
<button class="btn" type="button">Discard</button>
<button class="btn btn-primary" type="button">Save the reason</button>
</div>
</div>
@bind:event="oninput" and let it own the value. Toolbar edits mutate the
textarea and dispatch a bubbling input event so the binding picks them up — the
JS never calls back into .NET.
drSimpleUi.md.render() escapes all HTML first and only re-introduces a
fixed set of constructs, with link hrefs restricted to http:,
https:, mailto: and root-relative paths. It is not a
spec-complete parser. For untrusted input rendered to other users, sanitise server-side as
well rather than relying on this alone.