Fix: Tier 2 numbered list, sub-bullet checkmarks, Relocation Cost page, ISP FINANCING, green NB
Browse files
app/services/normalizer.py
CHANGED
|
@@ -129,6 +129,12 @@ def normalize_section(
|
|
| 129 |
# ── doc_v1 ──
|
| 130 |
if layout_norm == "doc_v1" and isinstance(section_json.get("blocks"), list):
|
| 131 |
blocks.extend(_normalize_doc_v1(section_json["blocks"], skip_title=title))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
return blocks
|
| 133 |
|
| 134 |
# ── Fallback ──
|
|
@@ -513,6 +519,230 @@ def _normalize_table_v2(json_data: dict) -> RenderBlock:
|
|
| 513 |
)
|
| 514 |
|
| 515 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
def _normalize_doc_v1(blocks: list, *, skip_title: str = "") -> list[RenderBlock]:
|
| 517 |
"""Normalise doc_v1 blocks into typed RenderBlocks.
|
| 518 |
|
|
|
|
| 129 |
# ── doc_v1 ──
|
| 130 |
if layout_norm == "doc_v1" and isinstance(section_json.get("blocks"), list):
|
| 131 |
blocks.extend(_normalize_doc_v1(section_json["blocks"], skip_title=title))
|
| 132 |
+
# Post-process breakdown section for Relocation Cost layout
|
| 133 |
+
if key_norm == "program_features_breakdown":
|
| 134 |
+
blocks = _postprocess_breakdown(blocks, section_json["blocks"])
|
| 135 |
+
# Post-process Tier 2 section for sub-bullet styling
|
| 136 |
+
if key_norm == "summary_of_universities_cosigner":
|
| 137 |
+
blocks = _postprocess_tier2(blocks)
|
| 138 |
return blocks
|
| 139 |
|
| 140 |
# ── Fallback ──
|
|
|
|
| 519 |
)
|
| 520 |
|
| 521 |
|
| 522 |
+
# ───────────────────────────────────────────────────────────────
|
| 523 |
+
# Breakdown section post-processor
|
| 524 |
+
# ───────────────────────────────────────────────────────────────
|
| 525 |
+
|
| 526 |
+
def _postprocess_breakdown(
|
| 527 |
+
blocks: list[RenderBlock],
|
| 528 |
+
raw_blocks: list,
|
| 529 |
+
) -> list[RenderBlock]:
|
| 530 |
+
"""Rewrite the breakdown section to match the reference layout.
|
| 531 |
+
|
| 532 |
+
- "Relocation Cost" becomes a banner heading with page-break-before
|
| 533 |
+
- The relocation table gets a merged right cell (rowspan) with the
|
| 534 |
+
cost-coverage note moved inside it
|
| 535 |
+
- "ISP FINANCING" becomes an inline note with mixed bold/italic
|
| 536 |
+
- "NB: CREDIT FACILITY" is styled green
|
| 537 |
+
- Dollar amounts in parentheticals keep their original $ format
|
| 538 |
+
"""
|
| 539 |
+
from markupsafe import Markup
|
| 540 |
+
|
| 541 |
+
# Find raw blocks for the relocation cost table (pre-normalised, $ intact)
|
| 542 |
+
raw_reloc_table = None
|
| 543 |
+
raw_note_after_table = None
|
| 544 |
+
found_reloc = False
|
| 545 |
+
for i, rb in enumerate(raw_blocks):
|
| 546 |
+
if not isinstance(rb, dict):
|
| 547 |
+
continue
|
| 548 |
+
if rb.get("type") == "subheading" and "relocation" in str(rb.get("text", "")).lower():
|
| 549 |
+
found_reloc = True
|
| 550 |
+
continue
|
| 551 |
+
if found_reloc and rb.get("type") == "table_v1" and raw_reloc_table is None:
|
| 552 |
+
raw_reloc_table = rb
|
| 553 |
+
continue
|
| 554 |
+
if found_reloc and raw_reloc_table and rb.get("type") == "paragraph" and raw_note_after_table is None:
|
| 555 |
+
raw_note_after_table = rb
|
| 556 |
+
break
|
| 557 |
+
|
| 558 |
+
result: list[RenderBlock] = []
|
| 559 |
+
i = 0
|
| 560 |
+
while i < len(blocks):
|
| 561 |
+
blk = blocks[i]
|
| 562 |
+
|
| 563 |
+
# ── Detect "Relocation Cost" heading ──
|
| 564 |
+
if (blk.block_type == "heading_2"
|
| 565 |
+
and "relocation" in blk.data.get("text", "").lower()):
|
| 566 |
+
|
| 567 |
+
# Banner heading with page break
|
| 568 |
+
result.append(RenderBlock(
|
| 569 |
+
block_type="heading_2",
|
| 570 |
+
css_class="hb-heading-2 hb-banner-heading page-break",
|
| 571 |
+
data={"text": blk.data["text"]},
|
| 572 |
+
))
|
| 573 |
+
i += 1
|
| 574 |
+
|
| 575 |
+
# Replace the next table with spanning variant that has merged cell
|
| 576 |
+
if i < len(blocks) and blocks[i].block_type == "table" and raw_reloc_table:
|
| 577 |
+
raw_rows = raw_reloc_table.get("rows", [])
|
| 578 |
+
# Build the note text for the merged right cell
|
| 579 |
+
note_text = ""
|
| 580 |
+
if raw_note_after_table:
|
| 581 |
+
note_text = str(raw_note_after_table.get("text", ""))
|
| 582 |
+
|
| 583 |
+
spanning_rows = _build_relocation_spanning_rows(raw_rows, note_text)
|
| 584 |
+
result.append(RenderBlock(
|
| 585 |
+
block_type="table",
|
| 586 |
+
css_class="hb-table hb-relocation-table",
|
| 587 |
+
data={"rows": spanning_rows, "variant": "spanning"},
|
| 588 |
+
))
|
| 589 |
+
i += 1 # skip the original table
|
| 590 |
+
|
| 591 |
+
# Skip the paragraph that was moved into the merged cell
|
| 592 |
+
if (i < len(blocks)
|
| 593 |
+
and blocks[i].block_type == "paragraph"
|
| 594 |
+
and note_text):
|
| 595 |
+
i += 1
|
| 596 |
+
continue
|
| 597 |
+
|
| 598 |
+
# ── "ISP FINANCING" heading → inline note with mixed formatting ──
|
| 599 |
+
if (blk.block_type == "heading_2"
|
| 600 |
+
and "isp financing" in blk.data.get("text", "").lower()):
|
| 601 |
+
# Next block should be the interest rate paragraph
|
| 602 |
+
rate_text = ""
|
| 603 |
+
if i + 1 < len(blocks) and blocks[i + 1].block_type == "paragraph":
|
| 604 |
+
rate_text = blocks[i + 1].data.get("text", "")
|
| 605 |
+
result.append(RenderBlock(
|
| 606 |
+
block_type="note",
|
| 607 |
+
css_class="hb-note hb-isp-financing",
|
| 608 |
+
data={
|
| 609 |
+
"parts": [
|
| 610 |
+
{"text": "ISP FINANCING", "style": "bold"},
|
| 611 |
+
{"text": " (" + _extract_rate_italic(rate_text) + "): " if rate_text else "", "style": "italic"},
|
| 612 |
+
{"text": _extract_rate_amount(rate_text), "style": "bold"},
|
| 613 |
+
],
|
| 614 |
+
"inline": True,
|
| 615 |
+
},
|
| 616 |
+
))
|
| 617 |
+
i += 1 # skip the heading
|
| 618 |
+
if rate_text:
|
| 619 |
+
i += 1 # skip the paragraph
|
| 620 |
+
continue
|
| 621 |
+
|
| 622 |
+
# ── "NB: CREDIT FACILITY" note → green styling ──
|
| 623 |
+
if (blk.block_type == "note"
|
| 624 |
+
and "credit facility" in blk.data.get("text", "").lower()):
|
| 625 |
+
result.append(RenderBlock(
|
| 626 |
+
block_type="note",
|
| 627 |
+
css_class="hb-note hb-credit-note",
|
| 628 |
+
data=blk.data,
|
| 629 |
+
))
|
| 630 |
+
i += 1
|
| 631 |
+
continue
|
| 632 |
+
|
| 633 |
+
result.append(blk)
|
| 634 |
+
i += 1
|
| 635 |
+
|
| 636 |
+
return result
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
def _build_relocation_spanning_rows(
|
| 640 |
+
raw_rows: list, note_text: str,
|
| 641 |
+
) -> list[list[dict]]:
|
| 642 |
+
"""Build spanning rows for the relocation cost table.
|
| 643 |
+
|
| 644 |
+
Row 0: normal 2-column (consultation fees | Covered in the contribution)
|
| 645 |
+
Rows 1-7: left cell per row, right cell merged (rowspan) with italic note
|
| 646 |
+
Rows 8+: left cell only, empty right
|
| 647 |
+
"""
|
| 648 |
+
from markupsafe import Markup
|
| 649 |
+
|
| 650 |
+
if not raw_rows:
|
| 651 |
+
return []
|
| 652 |
+
|
| 653 |
+
rows: list[list[dict]] = []
|
| 654 |
+
|
| 655 |
+
# Row 0 — has "Covered in the contribution"
|
| 656 |
+
first = raw_rows[0] if raw_rows else ["", ""]
|
| 657 |
+
rows.append([
|
| 658 |
+
{"text": Markup(emphasize_keywords(str(first[0] if len(first) > 0 else ""))), "colspan": 1, "rowspan": 1},
|
| 659 |
+
{"text": Markup("<em>" + h(str(first[1] if len(first) > 1 else "")) + "</em>"), "colspan": 1, "rowspan": 1},
|
| 660 |
+
])
|
| 661 |
+
|
| 662 |
+
# Rows 1-7: items with dollar amounts that get the merged right cell
|
| 663 |
+
# These are the visa/fee/rent/ticket rows (have parenthetical dollar amounts)
|
| 664 |
+
merged_start = 1
|
| 665 |
+
merged_end = min(8, len(raw_rows)) # Visa Integrity through Air ticket
|
| 666 |
+
|
| 667 |
+
for idx in range(merged_start, len(raw_rows)):
|
| 668 |
+
cell_text = str(raw_rows[idx][0] if len(raw_rows[idx]) > 0 else "")
|
| 669 |
+
left = {"text": Markup(emphasize_keywords(cell_text)), "colspan": 1, "rowspan": 1}
|
| 670 |
+
|
| 671 |
+
if idx == merged_start and note_text:
|
| 672 |
+
# First merged row gets the rowspan cell
|
| 673 |
+
span_count = merged_end - merged_start
|
| 674 |
+
note_html = note_text.replace("\n\n", "<br/><br/>")
|
| 675 |
+
right = {
|
| 676 |
+
"text": Markup('<em class="hb-merged-note">' + h(note_html).replace("<br/><br/>", "<br/><br/>") + "</em>"),
|
| 677 |
+
"colspan": 1,
|
| 678 |
+
"rowspan": span_count,
|
| 679 |
+
}
|
| 680 |
+
rows.append([left, right])
|
| 681 |
+
elif idx < merged_end:
|
| 682 |
+
# Subsequent merged rows — no right cell (covered by rowspan)
|
| 683 |
+
rows.append([left])
|
| 684 |
+
else:
|
| 685 |
+
# Remaining rows — empty right cell
|
| 686 |
+
rows.append([
|
| 687 |
+
left,
|
| 688 |
+
{"text": "", "colspan": 1, "rowspan": 1},
|
| 689 |
+
])
|
| 690 |
+
|
| 691 |
+
return rows
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
def _extract_rate_italic(text: str) -> str:
|
| 695 |
+
"""Extract the italic portion: 'Interest rate of 12% – 15% Market Rate PA'."""
|
| 696 |
+
# Text is like: "Interest rate of 12% – 15% Market Rate: UP TO USD 10,000"
|
| 697 |
+
m = re.match(r"(Interest rate.*?(?:Market Rate|PA))", text, re.IGNORECASE)
|
| 698 |
+
if m:
|
| 699 |
+
return m.group(1).rstrip(": ")
|
| 700 |
+
# Fallback: everything before the colon
|
| 701 |
+
if ":" in text:
|
| 702 |
+
return text.split(":")[0].strip()
|
| 703 |
+
return text
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
def _extract_rate_amount(text: str) -> str:
|
| 707 |
+
"""Extract the amount portion: 'UP TO USD 10,000'."""
|
| 708 |
+
m = re.search(r"(UP TO.*)", text, re.IGNORECASE)
|
| 709 |
+
if m:
|
| 710 |
+
return m.group(1).strip()
|
| 711 |
+
if ":" in text:
|
| 712 |
+
return text.split(":", 1)[1].strip()
|
| 713 |
+
return ""
|
| 714 |
+
|
| 715 |
+
|
| 716 |
+
# ───────────────────────────────────────────────────────────────
|
| 717 |
+
# Tier 2 (cosigner) section post-processor
|
| 718 |
+
# ───────────────────────────────────────────────────────────────
|
| 719 |
+
|
| 720 |
+
def _postprocess_tier2(blocks: list[RenderBlock]) -> list[RenderBlock]:
|
| 721 |
+
"""Style the Tier 2 section to match the reference layout.
|
| 722 |
+
|
| 723 |
+
- Second consecutive bullet_list (sub-bullets under Sources of Funds)
|
| 724 |
+
gets checkmark styling instead of arrows.
|
| 725 |
+
"""
|
| 726 |
+
result: list[RenderBlock] = []
|
| 727 |
+
prev_was_bullet = False
|
| 728 |
+
for blk in blocks:
|
| 729 |
+
if blk.block_type == "bullet_list":
|
| 730 |
+
if prev_was_bullet:
|
| 731 |
+
# This is the sub-bullet list → use checkmark class
|
| 732 |
+
result.append(RenderBlock(
|
| 733 |
+
block_type="bullet_list",
|
| 734 |
+
css_class="hb-bullet-list hb-sub-bullets",
|
| 735 |
+
data=blk.data,
|
| 736 |
+
))
|
| 737 |
+
else:
|
| 738 |
+
result.append(blk)
|
| 739 |
+
prev_was_bullet = True
|
| 740 |
+
else:
|
| 741 |
+
prev_was_bullet = False
|
| 742 |
+
result.append(blk)
|
| 743 |
+
return result
|
| 744 |
+
|
| 745 |
+
|
| 746 |
def _normalize_doc_v1(blocks: list, *, skip_title: str = "") -> list[RenderBlock]:
|
| 747 |
"""Normalise doc_v1 blocks into typed RenderBlocks.
|
| 748 |
|
app/static/css/print.css
CHANGED
|
@@ -215,7 +215,7 @@ ul.hb-bullet-list li::before {
|
|
| 215 |
.hb-numbered-list,
|
| 216 |
.ol,
|
| 217 |
ol.hb-numbered-list {
|
| 218 |
-
list-style: decimal;
|
| 219 |
margin: 0 0 8pt 18pt;
|
| 220 |
padding: 0;
|
| 221 |
font-size: 9pt;
|
|
@@ -234,7 +234,18 @@ ol.hb-numbered-list li {
|
|
| 234 |
.hb-numbered-list li::before,
|
| 235 |
.ol li::before,
|
| 236 |
ol.hb-numbered-list li::before {
|
| 237 |
-
content: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
}
|
| 239 |
|
| 240 |
/* ------------------------------
|
|
@@ -1088,4 +1099,75 @@ table.programs td:nth-child(5) {
|
|
| 1088 |
.page-content img {
|
| 1089 |
max-width: 100%;
|
| 1090 |
height: auto;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1091 |
}
|
|
|
|
| 215 |
.hb-numbered-list,
|
| 216 |
.ol,
|
| 217 |
ol.hb-numbered-list {
|
| 218 |
+
list-style: decimal !important;
|
| 219 |
margin: 0 0 8pt 18pt;
|
| 220 |
padding: 0;
|
| 221 |
font-size: 9pt;
|
|
|
|
| 234 |
.hb-numbered-list li::before,
|
| 235 |
.ol li::before,
|
| 236 |
ol.hb-numbered-list li::before {
|
| 237 |
+
content: none !important;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
/* Sub-bullets — checkmarks, indented under parent bullet */
|
| 241 |
+
.hb-sub-bullets {
|
| 242 |
+
margin-left: 32pt !important;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
.hb-sub-bullets li::before {
|
| 246 |
+
content: "\2713" !important;
|
| 247 |
+
color: #000000;
|
| 248 |
+
font-size: 9pt;
|
| 249 |
}
|
| 250 |
|
| 251 |
/* ------------------------------
|
|
|
|
| 1099 |
.page-content img {
|
| 1100 |
max-width: 100%;
|
| 1101 |
height: auto;
|
| 1102 |
+
}
|
| 1103 |
+
|
| 1104 |
+
/* ------------------------------
|
| 1105 |
+
BREAKDOWN — RELOCATION COST
|
| 1106 |
+
------------------------------ */
|
| 1107 |
+
|
| 1108 |
+
/* Banner heading (teal background bar) */
|
| 1109 |
+
.hb-banner-heading {
|
| 1110 |
+
background-color: #199970;
|
| 1111 |
+
color: #FFFFFF !important;
|
| 1112 |
+
padding: 6pt 10pt;
|
| 1113 |
+
margin: 10pt 0 6pt;
|
| 1114 |
+
font-size: 11pt;
|
| 1115 |
+
font-weight: 700;
|
| 1116 |
+
}
|
| 1117 |
+
|
| 1118 |
+
/* Page break before Relocation Cost */
|
| 1119 |
+
.sec-breakdown .page-break {
|
| 1120 |
+
page-break-before: always;
|
| 1121 |
+
break-before: page;
|
| 1122 |
+
}
|
| 1123 |
+
|
| 1124 |
+
/* Relocation cost table — merged note cell */
|
| 1125 |
+
.hb-relocation-table {
|
| 1126 |
+
margin: 0 0 10pt;
|
| 1127 |
+
}
|
| 1128 |
+
|
| 1129 |
+
.hb-relocation-table td {
|
| 1130 |
+
vertical-align: top;
|
| 1131 |
+
padding: 3pt 6pt;
|
| 1132 |
+
border: 1pt solid #CCCCCC;
|
| 1133 |
+
font-size: 9.5pt;
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
.hb-merged-note {
|
| 1137 |
+
font-style: italic;
|
| 1138 |
+
font-weight: 400;
|
| 1139 |
+
font-size: 9.5pt;
|
| 1140 |
+
line-height: 1.4;
|
| 1141 |
+
display: block;
|
| 1142 |
+
padding: 4pt 2pt;
|
| 1143 |
+
}
|
| 1144 |
+
|
| 1145 |
+
/* ISP FINANCING line */
|
| 1146 |
+
.hb-isp-financing {
|
| 1147 |
+
margin: 14pt 0 6pt;
|
| 1148 |
+
padding: 6pt 0;
|
| 1149 |
+
border-top: 1.5pt solid #000000;
|
| 1150 |
+
border-bottom: 1.5pt solid #000000;
|
| 1151 |
+
text-align: center;
|
| 1152 |
+
font-size: 10pt;
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
.hb-isp-financing strong {
|
| 1156 |
+
font-weight: 700;
|
| 1157 |
+
color: #000000;
|
| 1158 |
+
}
|
| 1159 |
+
|
| 1160 |
+
.hb-isp-financing em {
|
| 1161 |
+
font-style: italic;
|
| 1162 |
+
color: #199970;
|
| 1163 |
+
font-weight: 700;
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
/* NB: CREDIT FACILITY — green */
|
| 1167 |
+
.hb-credit-note {
|
| 1168 |
+
text-align: center;
|
| 1169 |
+
color: #199970;
|
| 1170 |
+
font-size: 10pt;
|
| 1171 |
+
font-weight: 700;
|
| 1172 |
+
margin: 6pt 0;
|
| 1173 |
}
|
app/templates/partials/blocks/heading.html
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{# Block partial: heading_1 / heading_2 #}
|
| 2 |
{% if block.block_type == 'heading_1' %}
|
| 3 |
-
<h2 class="hb-heading-1">{{ block.data.text | e }}</h2>
|
| 4 |
{% elif block.block_type == 'heading_2' %}
|
| 5 |
-
<h3 class="hb-heading-2">{{ block.data.text | e }}</h3>
|
| 6 |
{% endif %}
|
|
|
|
| 1 |
{# Block partial: heading_1 / heading_2 #}
|
| 2 |
{% if block.block_type == 'heading_1' %}
|
| 3 |
+
<h2 class="{{ block.css_class | default('hb-heading-1') }}">{{ block.data.text | e }}</h2>
|
| 4 |
{% elif block.block_type == 'heading_2' %}
|
| 5 |
+
<h3 class="{{ block.css_class | default('hb-heading-2') }}">{{ block.data.text | e }}</h3>
|
| 6 |
{% endif %}
|
app/templates/partials/blocks/note.html
CHANGED
|
@@ -1,16 +1,20 @@
|
|
| 1 |
{# Block partial: note (standalone or inline-parts) #}
|
| 2 |
{% if block.data.inline and block.data.parts %}
|
| 3 |
-
<div class="hb-note">
|
| 4 |
{% for part in block.data.parts %}
|
| 5 |
{% if part.style == 'red_bold' %}
|
| 6 |
<strong class="hb-note-keyword">{{ part.text | e }}</strong>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
{% else %}
|
| 8 |
{{ part.text | e }}
|
| 9 |
{% endif %}
|
| 10 |
{% endfor %}
|
| 11 |
</div>
|
| 12 |
{% else %}
|
| 13 |
-
<div class="hb-note">
|
| 14 |
{% set text = block.data.text | default('') %}
|
| 15 |
{# Highlight NOTE / ONLY IF keywords in bold + red; rest stays bold via CSS #}
|
| 16 |
{% if text.upper().startswith('NOTE:') %}
|
|
|
|
| 1 |
{# Block partial: note (standalone or inline-parts) #}
|
| 2 |
{% if block.data.inline and block.data.parts %}
|
| 3 |
+
<div class="{{ block.css_class | default('hb-note') }}">
|
| 4 |
{% for part in block.data.parts %}
|
| 5 |
{% if part.style == 'red_bold' %}
|
| 6 |
<strong class="hb-note-keyword">{{ part.text | e }}</strong>
|
| 7 |
+
{% elif part.style == 'bold' %}
|
| 8 |
+
<strong>{{ part.text | e }}</strong>
|
| 9 |
+
{% elif part.style == 'italic' %}
|
| 10 |
+
<em>{{ part.text | e }}</em>
|
| 11 |
{% else %}
|
| 12 |
{{ part.text | e }}
|
| 13 |
{% endif %}
|
| 14 |
{% endfor %}
|
| 15 |
</div>
|
| 16 |
{% else %}
|
| 17 |
+
<div class="{{ block.css_class | default('hb-note') }}">
|
| 18 |
{% set text = block.data.text | default('') %}
|
| 19 |
{# Highlight NOTE / ONLY IF keywords in bold + red; rest stays bold via CSS #}
|
| 20 |
{% if text.upper().startswith('NOTE:') %}
|