SOA as document: replace export with publish workflow
Statements of Applicability are no longer exported as one-off PDFs. Instead, each SOA owns a persistent document that accumulates versions over time, following the same publish/approve lifecycle as authored documents. Publishing without approvers publishes immediately; publishing with approvers creates a draft pending approval via the existing quorum system. SOAs can also store default approvers that are pre-populated in the publish dialog. The SOA is removed from the snapshot system — applicability statements are now queried directly (snapshot_id IS NULL) rather than through snapshot copies. A standalone migration script (cmd/migrate-soa-snapshots-to-documents) converts existing SOA snapshots into documents with proper ProseMirror content, preserving version history and approval decisions. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -44,9 +44,6 @@ var (
|
||||
//go:embed transfer_impact_assessments_template.html
|
||||
transferImpactAssessmentsTemplateContent string
|
||||
|
||||
//go:embed soa_template.html
|
||||
soaTemplateContent string
|
||||
|
||||
templateFuncs = template.FuncMap{
|
||||
"now": func() time.Time { return time.Now() },
|
||||
"eq": func(a, b any) bool { return a == b },
|
||||
@@ -186,8 +183,6 @@ var (
|
||||
dataProtectionImpactAssessmentsTemplate = template.Must(template.New("dataProtectionImpactAssessments").Funcs(templateFuncs).Parse(dataProtectionImpactAssessmentsTemplateContent))
|
||||
|
||||
transferImpactAssessmentsTemplate = template.Must(template.New("transferImpactAssessments").Funcs(templateFuncs).Parse(transferImpactAssessmentsTemplateContent))
|
||||
|
||||
statementOfApplicabilityTemplate = template.Must(template.New("statement-of-applicability").Funcs(templateFuncs).Parse(soaTemplateContent))
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -205,6 +200,7 @@ type (
|
||||
Signatures []SignatureData
|
||||
CompanyHorizontalLogoBase64 string
|
||||
MermaidJS template.JS
|
||||
Landscape bool
|
||||
}
|
||||
|
||||
SignatureData struct {
|
||||
@@ -280,37 +276,35 @@ type (
|
||||
}
|
||||
|
||||
StatementOfApplicabilityData struct {
|
||||
Title string
|
||||
OrganizationName string
|
||||
CreatedAt time.Time
|
||||
TotalControls int
|
||||
FrameworkGroups []FrameworkControlGroup
|
||||
CompanyHorizontalLogoBase64 string
|
||||
Version int
|
||||
PublishedAt time.Time
|
||||
Approver string
|
||||
Title string
|
||||
OrganizationName string
|
||||
CreatedAt time.Time
|
||||
TotalControls int
|
||||
Rows []SOARow
|
||||
}
|
||||
|
||||
FrameworkControlGroup struct {
|
||||
FrameworkName string
|
||||
Controls []ControlData
|
||||
}
|
||||
|
||||
ControlData struct {
|
||||
FrameworkName string
|
||||
SectionTitle string
|
||||
Name string
|
||||
Applicability *bool
|
||||
Justification *string
|
||||
BestPractice *bool
|
||||
Implemented *string
|
||||
NotImplementedJustification *string
|
||||
Regulatory *bool
|
||||
Contractual *bool
|
||||
RiskAssessment *bool
|
||||
SOARow struct {
|
||||
FrameworkName string
|
||||
ControlSection string
|
||||
ControlName string
|
||||
Applicability string
|
||||
Justification string
|
||||
Implemented string
|
||||
NotImplJustification string
|
||||
Regulatory string
|
||||
Contractual string
|
||||
BestPractice string
|
||||
RiskAssessment string
|
||||
}
|
||||
)
|
||||
|
||||
func BoolLabel(v bool) string {
|
||||
if v {
|
||||
return "Yes"
|
||||
}
|
||||
return "No"
|
||||
}
|
||||
|
||||
const (
|
||||
ClassificationPublic Classification = "PUBLIC"
|
||||
ClassificationInternal Classification = "INTERNAL"
|
||||
@@ -381,12 +375,3 @@ func RenderTransferImpactAssessmentsTableHTML(data TransferImpactAssessmentTable
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func RenderStatementOfApplicabilityHTML(data StatementOfApplicabilityData) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := statementOfApplicabilityTemplate.Execute(&buf, data); err != nil {
|
||||
return nil, fmt.Errorf("cannot execute SOA template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
@@ -1,523 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Statement of Applicability</title>
|
||||
<style>
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 2.5cm;
|
||||
@bottom-right {
|
||||
content: "Page " counter(page) " of " counter(pages);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 7.5pt;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Cover page */
|
||||
.cover-page {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.company-header {
|
||||
margin-bottom: 30px;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-height: 50px;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.export-title {
|
||||
font-size: 22pt;
|
||||
font-weight: normal;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 25px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.export-subtitle {
|
||||
font-size: 18pt;
|
||||
font-weight: normal;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 25px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.meta-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.meta-table td {
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #333;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.meta-table td:first-child {
|
||||
font-weight: 600;
|
||||
width: 25%;
|
||||
background: #f8f8f8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.classification {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.purpose-section {
|
||||
margin: 30px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-text {
|
||||
font-size: 10pt;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
/* Controls page */
|
||||
.controls-page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.controls-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.controls-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 8pt;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.controls-table th,
|
||||
.controls-table td {
|
||||
padding: 5px 6px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.controls-table th {
|
||||
background: #f5f5f5;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.controls-table tr {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.section-tag {
|
||||
display: inline-block;
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 7pt;
|
||||
font-weight: 500;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.state-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 8pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.state-tag-success {
|
||||
background: #eefadc;
|
||||
color: #5d770d;
|
||||
}
|
||||
|
||||
.state-tag-warning {
|
||||
background: #fff4d5;
|
||||
color: #ad5700;
|
||||
}
|
||||
|
||||
.state-tag-danger {
|
||||
background: #ffefef;
|
||||
color: #cd2b31;
|
||||
}
|
||||
|
||||
/* Annex page */
|
||||
.annex-page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.annex-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.annex-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.annex-section-title {
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 15px 0 10px 0;
|
||||
}
|
||||
|
||||
.annex-subsection-title {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
.annex-enum-list {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.annex-enum-item {
|
||||
margin-bottom: 8px;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.annex-enum-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.annex-enum-description {
|
||||
color: #000;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Prevent bad page breaks */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-after: avoid;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cover-page">
|
||||
<div class="company-header">
|
||||
{{- if .CompanyHorizontalLogoBase64}}
|
||||
{{imgTag .CompanyHorizontalLogoBase64 "Company Logo" "company-logo"}}
|
||||
{{- end}}
|
||||
</div>
|
||||
|
||||
<h1 class="export-title">Statement of Applicability</h1>
|
||||
<h2 class="export-subtitle">{{.Title}}</h2>
|
||||
|
||||
<div class="document-meta">
|
||||
<table class="meta-table">
|
||||
<tr>
|
||||
<td>Classification</td>
|
||||
<td>
|
||||
<span class="classification">CONFIDENTIAL</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Approver</td>
|
||||
<td>{{.Approver}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{{.Version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Published</td>
|
||||
<td>{{.PublishedAt.Format "January 2, 2006"}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="purpose-section">
|
||||
<div class="purpose-title">1. Purpose</div>
|
||||
<div class="purpose-text">
|
||||
This document provides a comprehensive overview of the statement of applicability for controls within the organization.
|
||||
It serves as a record of which controls are applicable or not applicable to the organization, along with their
|
||||
relationships to regulatory requirements, contractual obligations, risk assessments, and best practices.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{- if .FrameworkGroups}}
|
||||
<div class="controls-page">
|
||||
<h1 class="controls-title">2. Controls</h1>
|
||||
<table class="controls-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" style="width: 12%;">Framework</th>
|
||||
<th rowspan="2" style="width: 24%;">Control</th>
|
||||
<th rowspan="2" style="width: 8%;">Applicability</th>
|
||||
<th rowspan="2" style="width: 14%;">Justification for non-applicability</th>
|
||||
<th rowspan="2" style="width: 8%;">Implemented</th>
|
||||
<th rowspan="2" style="width: 10%;">Justification for non-implementation</th>
|
||||
<th colspan="4" style="width: 24%; text-align: center;">Justification for inclusion</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width: 6%;">Regulatory</th>
|
||||
<th style="width: 6%;">Contractual</th>
|
||||
<th style="width: 6%;">Best Practice</th>
|
||||
<th style="width: 6%;">Risk Assessment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{- range $group := .FrameworkGroups}}
|
||||
{{- range $group.Controls}}
|
||||
<tr>
|
||||
<td>{{$group.FrameworkName}}</td>
|
||||
<td><span class="section-tag">{{.SectionTitle}}</span>{{.Name}}</td>
|
||||
<td>
|
||||
{{- $state := boolToYesNo .Applicability}}
|
||||
{{- if eq $state "yes"}}
|
||||
<span class="state-tag state-tag-success">Yes</span>
|
||||
{{- else if eq $state "no"}}
|
||||
<span class="state-tag state-tag-danger">No</span>
|
||||
{{- else}}
|
||||
<span class="state-tag">-</span>
|
||||
{{- end}}
|
||||
</td>
|
||||
<td>
|
||||
{{- $appStateJ := boolToYesNo .Applicability}}
|
||||
{{- if and (eq $appStateJ "no") .Justification}}
|
||||
{{.Justification}}
|
||||
{{- else}}
|
||||
-
|
||||
{{- end}}
|
||||
</td>
|
||||
<td>
|
||||
{{- $appState := boolToYesNo .Applicability}}
|
||||
{{- if eq $appState "no"}}
|
||||
<span class="state-tag">-</span>
|
||||
{{- else if .Implemented}}
|
||||
{{- if eq (derefString .Implemented) "IMPLEMENTED"}}
|
||||
<span class="state-tag state-tag-success">Yes</span>
|
||||
{{- else}}
|
||||
<span class="state-tag state-tag-danger">No</span>
|
||||
{{- end}}
|
||||
{{- else}}
|
||||
<span class="state-tag">-</span>
|
||||
{{- end}}
|
||||
</td>
|
||||
<td>
|
||||
{{- $appState2 := boolToYesNo .Applicability}}
|
||||
{{- if eq $appState2 "no"}}
|
||||
-
|
||||
{{- else if and .Implemented (eq (derefString .Implemented) "NOT_IMPLEMENTED") .NotImplementedJustification}}
|
||||
{{.NotImplementedJustification}}
|
||||
{{- else}}
|
||||
-
|
||||
{{- end}}
|
||||
</td>
|
||||
<td>{{boolToYesNoDash .Regulatory}}</td>
|
||||
<td>{{boolToYesNoDash .Contractual}}</td>
|
||||
<td>{{boolToYesNoDash .BestPractice}}</td>
|
||||
<td>{{boolToYesNoDash .RiskAssessment}}</td>
|
||||
</tr>
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
<div class="annex-page">
|
||||
<h1 class="annex-title">3. Annexes</h1>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-section-title">3.1 Column Definitions</div>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Framework</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-description">The name of the compliance framework or standard to which the control belongs (e.g., ISO 27001, SOC 2, GDPR).</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Control</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-description">The specific control identifier and name within the framework, including its section reference.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Applicability</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is applicable to the organization.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not applicable to the organization (with justification provided).</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Justification for non-applicability</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-description">Provides the rationale when a control is not applicable. This field is empty for applicable controls.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Implemented</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control has been implemented by the organization.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control has not been implemented (with justification provided).</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Justification for non-implementation</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-description">Provides the rationale when a control is not implemented. This field is empty for implemented controls or when the control is not applicable.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Justification for inclusion</div>
|
||||
<div class="annex-enum-description" style="margin-bottom: 12px;">
|
||||
For applicable controls, this section provides additional context on why the control is included, based on regulatory requirements, contractual obligations, best practices, or risk assessments.
|
||||
</div>
|
||||
|
||||
<div style="margin-left: 20px;">
|
||||
<div class="annex-subsection-title" style="font-size: 9pt; margin-top: 10px;">Regulatory</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is linked to one or more legal or regulatory obligations.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not associated with any legal or regulatory obligations.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="annex-subsection-title" style="font-size: 9pt; margin-top: 10px;">Contractual</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is linked to one or more contractual obligations.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not associated with any contractual obligations.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="annex-subsection-title" style="font-size: 9pt; margin-top: 10px;">Best Practice</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is designated as a best practice recommendation.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not designated as a best practice.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="annex-subsection-title" style="font-size: 9pt; margin-top: 10px;">Risk Assessment</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The control is associated with one or more identified risks through risk mitigation measures.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The control is not currently associated with any identified risks.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">-:</span>
|
||||
<span class="annex-enum-description">Not applicable (control is not applicable).</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,7 +8,7 @@
|
||||
<style>
|
||||
/* A4 Page Setup for printing */
|
||||
@page {
|
||||
size: A4;
|
||||
size: A4{{if .Landscape}} landscape{{end}};
|
||||
margin: 2.5cm;
|
||||
|
||||
@bottom-right {
|
||||
@@ -118,8 +118,16 @@
|
||||
widows: 3;
|
||||
}
|
||||
|
||||
.document-content hr {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 0;
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.document-content h1 {
|
||||
font-size: 14pt;
|
||||
font-size: 18pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 20px 0 12px 0;
|
||||
|
||||
Reference in New Issue
Block a user