Skip to content

Commit be8d2d0

Browse files
committed
updated markdown to html - tables, faq
Signed-off-by: bidi47 <bidi@apidemia.com>
1 parent 02a4609 commit be8d2d0

6 files changed

Lines changed: 97 additions & 10 deletions

File tree

‎src/App/assets/scss/components/_blog.scss‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,57 @@ $bp-tablet: 900px;
240240
font-size: inherit;
241241
}
242242
}
243+
244+
// Markdown tables render as bare <table> elements, so they are styled by tag.
245+
// display:block lets a wide table scroll sideways instead of overflowing the column.
246+
table {
247+
display: block;
248+
width: 100%;
249+
max-width: 100%;
250+
margin: 0 0 24px;
251+
overflow-x: auto;
252+
border: 1px solid var(--border);
253+
border-radius: 8px;
254+
border-collapse: collapse;
255+
font-size: 14.5px;
256+
background: var(--surface);
257+
258+
th,
259+
td {
260+
padding: 12px 16px;
261+
text-align: left;
262+
vertical-align: top;
263+
border-bottom: 1px solid var(--border);
264+
color: var(--text);
265+
}
266+
267+
thead th {
268+
font-family: var(--mono);
269+
font-size: 12px;
270+
letter-spacing: 0.06em;
271+
text-transform: uppercase;
272+
white-space: nowrap;
273+
color: var(--text-dim);
274+
background: var(--surface-2);
275+
}
276+
277+
tbody tr:last-child td,
278+
tbody tr:last-child th {
279+
border-bottom: 0;
280+
}
281+
282+
tbody th,
283+
td:first-child {
284+
font-weight: 500;
285+
color: var(--text-muted);
286+
}
287+
288+
p {
289+
margin-bottom: 0;
290+
font-size: inherit;
291+
line-height: 1.6;
292+
}
293+
}
243294
}
244295

245296
// ---------- Obsolete post warning ----------

‎src/App/src/Service/FaqExtractor.php‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414
/**
1515
* Pulls the trailing `## FAQ` section (a `**Q: ...**` / `A: ...` pair per question) out of a
1616
* Markdown article body, so it can be rendered as an accordion and fed into JSON-LD separately
17-
* from the rest of the body.
17+
* from the rest of the body. `question` and `answer` keep their Markdown (for rendering);
18+
* `questionText` and `answerText` are plain-text copies (for JSON-LD).
1819
*/
1920
final class FaqExtractor
2021
{
2122
/**
22-
* @return array{faq: list<array{question: string, answer: string}>, body: string}
23+
* @return array{
24+
* faq: list<array{question: string, answer: string, questionText: string, answerText: string}>,
25+
* body: string
26+
* }
2327
*/
2428
public static function extract(string $body): array
2529
{
@@ -35,14 +39,24 @@ public static function extract(string $body): array
3539
PREG_SET_ORDER
3640
);
3741
foreach ($matches as $match) {
42+
$question = trim($match[1]);
43+
$answer = trim(preg_replace('/\s+/', ' ', $match[2]) ?? '');
44+
3845
$faq[] = [
39-
'question' => trim($match[1]),
40-
'answer' => trim(preg_replace('/\s+/', ' ', $match[2]) ?? ''),
46+
'question' => $question,
47+
'answer' => $answer,
48+
'questionText' => self::toPlainText($question),
49+
'answerText' => self::toPlainText($answer),
4150
];
4251
}
4352

4453
$body = trim((string) preg_replace('/^## FAQ\s*$.*\z/ms', '', $body));
4554

4655
return ['faq' => $faq, 'body' => $body];
4756
}
57+
58+
private static function toPlainText(string $markdown): string
59+
{
60+
return preg_replace('/(`+)(.+?)\1/', '$2', $markdown) ?? $markdown;
61+
}
4862
}

‎src/App/templates/layout/blog-post.html.twig‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq",
4646
"mainEntity": [
4747
{% for item in faq %}
48-
{ "@type": "Question", "name": {{ item.question|json_encode|raw }}, "acceptedAnswer": { "@type": "Answer", "text": {{ item.answer|json_encode|raw }} } }{% if not loop.last %},{% endif %}
48+
{ "@type": "Question", "name": {{ item.questionText|json_encode|raw }}, "acceptedAnswer": { "@type": "Answer", "text": {{ item.answerText|json_encode|raw }} } }{% if not loop.last %},{% endif %}
4949
{% endfor %}
5050
]
5151
}{% endif %}

‎src/Blog/templates/page/markdown-article.html.twig‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
<div class="faq-list">
1010
{% for item in faq %}
1111
<details class="acc"{% if loop.first %} open{% endif %}>
12-
<summary>{{ item.question }} <span class="chev">+</span></summary>
12+
<summary>{{ item.question|markdown_to_html|striptags('<code><strong><em><a>')|raw }} <span class="chev">+</span></summary>
1313
<div class="acc-body">
14-
<p class="mb-0">{{ item.answer }}</p>
14+
<p class="mb-0">{{ item.answer|markdown_to_html|striptags('<code><strong><em><a>')|raw }}</p>
1515
</div>
1616
</details>
1717
{% endfor %}

‎test/Unit/App/Service/FaqExtractorTest.php‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,35 @@ public function testExtractPullsTheQuestionAnswerPairsOutOfTheFaqSection(): void
2020
$this->assertSame('Some intro text.', $result['body']);
2121
$this->assertSame(
2222
[
23-
['question' => 'What is it?', 'answer' => "It's an example."],
24-
['question' => 'Why does it matter?', 'answer' => 'Because tests need coverage.'],
23+
[
24+
'question' => 'What is it?',
25+
'answer' => "It's an example.",
26+
'questionText' => 'What is it?',
27+
'answerText' => "It's an example.",
28+
],
29+
[
30+
'question' => 'Why does it matter?',
31+
'answer' => 'Because tests need coverage.',
32+
'questionText' => 'Why does it matter?',
33+
'answerText' => 'Because tests need coverage.',
34+
],
2535
],
2636
$result['faq']
2737
);
2838
}
2939

40+
public function testExtractKeepsMarkdownForRenderingAndStripsBackticksForPlainText(): void
41+
{
42+
$body = "## FAQ\n\n**Q: Is `dot-cache` needed?**\nA: Use `dot-cache` and ``dot-cli``.";
43+
44+
$result = FaqExtractor::extract($body);
45+
46+
$this->assertSame('Is `dot-cache` needed?', $result['faq'][0]['question']);
47+
$this->assertSame('Use `dot-cache` and ``dot-cli``.', $result['faq'][0]['answer']);
48+
$this->assertSame('Is dot-cache needed?', $result['faq'][0]['questionText']);
49+
$this->assertSame('Use dot-cache and dot-cli.', $result['faq'][0]['answerText']);
50+
}
51+
3052
public function testExtractReturnsAnEmptyFaqWhenThereIsNoFaqSection(): void
3153
{
3254
$body = "Some intro text.\n\nMore content.";

‎vite.config.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ export default defineConfig({
2020
}),
2121
ViteMinifyPlugin({}),
2222
],
23-
emptyOutDir: true,
2423
root: path.resolve(__dirname, 'src'), // Set the root directory for Vite
2524
build: {
2625
outDir: '../public', // Output directory for compiled assets
26+
emptyOutDir: false,
2727
rollupOptions: {
2828
input: {
2929
main: '/App/assets/js/index.js', // Main JavaScript entry point

0 commit comments

Comments
 (0)