releasehx 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +363 -330
  3. data/build/docs/_config.yml +1 -0
  4. data/build/docs/_release_index.adoc +3 -2
  5. data/build/docs/config-reference.adoc +197 -10
  6. data/build/docs/config-reference.json +56 -7
  7. data/build/docs/index.adoc +315 -59
  8. data/build/docs/landing.adoc +1 -1
  9. data/build/docs/manpage.adoc +2 -2
  10. data/build/docs/release-procedure.adoc +365 -0
  11. data/build/docs/release-procedure.html +87 -0
  12. data/build/docs/releasehx.1 +17 -5
  13. data/build/docs/sample-config.yml +14 -7
  14. data/lib/releasehx/cli.rb +5 -2
  15. data/lib/releasehx/configuration.rb +0 -1
  16. data/lib/releasehx/generated.rb +1 -1
  17. data/lib/releasehx/mcp/assets/agent-config-guide.md +1 -1
  18. data/lib/releasehx/mcp/assets/config-def.yml +122 -6
  19. data/lib/releasehx/mcp/assets/config-reference.adoc +197 -10
  20. data/lib/releasehx/mcp/assets/config-reference.json +56 -7
  21. data/lib/releasehx/mcp/assets/sample-config.yml +14 -7
  22. data/lib/releasehx/mcp/server.rb +0 -1
  23. data/lib/releasehx/ops/enrich_ops.rb +161 -55
  24. data/lib/releasehx/ops/template_ops.rb +1 -1
  25. data/lib/releasehx/rhyml/adapter.rb +0 -3
  26. data/lib/releasehx/rhyml/templates/bootstrap-overrides.css +15 -0
  27. data/lib/releasehx/rhyml/templates/changelog.adoc.liquid +2 -0
  28. data/lib/releasehx/rhyml/templates/changelog.html.liquid +6 -4
  29. data/lib/releasehx/rhyml/templates/changelog.md.liquid +1 -0
  30. data/lib/releasehx/rhyml/templates/embedded.css.liquid +263 -0
  31. data/lib/releasehx/rhyml/templates/entry.adoc.liquid +1 -0
  32. data/lib/releasehx/rhyml/templates/entry.html.liquid +21 -20
  33. data/lib/releasehx/rhyml/templates/entry.md.liquid +15 -21
  34. data/lib/releasehx/rhyml/templates/head-parser.liquid +6 -2
  35. data/lib/releasehx/rhyml/templates/header.liquid +13 -4
  36. data/lib/releasehx/rhyml/templates/history.html.liquid +152 -33
  37. data/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid +83 -38
  38. data/lib/releasehx/rhyml/templates/metadata-entry.html.liquid +60 -1
  39. data/lib/releasehx/rhyml/templates/metadata-entry.md.liquid +65 -113
  40. data/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid +83 -38
  41. data/lib/releasehx/rhyml/templates/metadata-note.html.liquid +59 -22
  42. data/lib/releasehx/rhyml/templates/metadata-note.md.liquid +68 -23
  43. data/lib/releasehx/rhyml/templates/note.html.liquid +25 -19
  44. data/lib/releasehx/rhyml/templates/note.md.liquid +44 -26
  45. data/lib/releasehx/rhyml/templates/release-notes.adoc.liquid +2 -0
  46. data/lib/releasehx/rhyml/templates/release-notes.html.liquid +6 -4
  47. data/lib/releasehx/rhyml/templates/release-notes.md.liquid +1 -0
  48. data/lib/releasehx/rhyml/templates/release.adoc.liquid +2 -0
  49. data/lib/releasehx/rhyml/templates/release.md.liquid +8 -7
  50. data/lib/releasehx/rhyml/templates/rhyml-change.yaml.liquid +36 -36
  51. data/lib/releasehx/rhyml/templates/wrapper.html.liquid +103 -0
  52. data/lib/releasehx/sgyml/helpers.rb +0 -2
  53. data/lib/releasehx/transforms/adf_to_markdown.rb +1 -1
  54. data/lib/releasehx/version.rb +0 -2
  55. data/lib/releasehx.rb +2 -2
  56. data/specs/data/config-def.yml +122 -6
  57. metadata +48 -25
  58. data/build/docs/schemagraphy_readme.html +0 -0
  59. data/build/docs/sourcerer_readme.html +0 -46
  60. data/lib/schemagraphy/attribute_resolver.rb +0 -48
  61. data/lib/schemagraphy/cfgyml/definition.rb +0 -90
  62. data/lib/schemagraphy/cfgyml/doc_builder.rb +0 -52
  63. data/lib/schemagraphy/cfgyml/path_reference.rb +0 -24
  64. data/lib/schemagraphy/data_query/json_pointer.rb +0 -42
  65. data/lib/schemagraphy/loader.rb +0 -59
  66. data/lib/schemagraphy/regexp_utils.rb +0 -235
  67. data/lib/schemagraphy/safe_expression.rb +0 -189
  68. data/lib/schemagraphy/schema_utils.rb +0 -124
  69. data/lib/schemagraphy/tag_utils.rb +0 -32
  70. data/lib/schemagraphy/templating.rb +0 -104
  71. data/lib/schemagraphy.rb +0 -17
  72. data/lib/sourcerer/builder.rb +0 -120
  73. data/lib/sourcerer/jekyll/bootstrapper.rb +0 -78
  74. data/lib/sourcerer/jekyll/liquid/file_system.rb +0 -74
  75. data/lib/sourcerer/jekyll/liquid/filters.rb +0 -215
  76. data/lib/sourcerer/jekyll/liquid/tags.rb +0 -44
  77. data/lib/sourcerer/jekyll/monkeypatches.rb +0 -73
  78. data/lib/sourcerer/jekyll.rb +0 -26
  79. data/lib/sourcerer/plaintext_converter.rb +0 -75
  80. data/lib/sourcerer/templating.rb +0 -190
  81. data/lib/sourcerer.rb +0 -322
@@ -34,7 +34,16 @@ module ReleaseHx
34
34
  when :html
35
35
  # Direct Liquid template rendering to HTML
36
36
  html_content = DraftOps.process_template_content(release: release, config: config, format: :html)
37
- WriteOps.safe_write(outpath, html_content)
37
+
38
+ # Wrap HTML with Bootstrap styling if enabled
39
+ enriched = if config && config.dig('modes', 'html_wrap') != false
40
+ ReleaseHx.logger.debug('Applying HTML wrapper')
41
+ wrap_html(html_content, config)
42
+ else
43
+ html_content
44
+ end
45
+
46
+ WriteOps.safe_write(outpath, enriched)
38
47
  when :pdf
39
48
  # Two-stage process: RHYML → AsciiDoc → PDF
40
49
  asciidoc_content = DraftOps.process_template_content(release: release, config: config, format: :adoc)
@@ -49,7 +58,7 @@ module ReleaseHx
49
58
  # Generates rich-text output from source draft files of various formats.
50
59
  #
51
60
  # Automatically detects the input file type and applies the appropriate conversion
52
- # strategy, supporting YAML (via RHYML), Markdown, and AsciiDoc source formats.
61
+ # strategy, supporting YAML (via RHYML) and AsciiDoc source formats.
53
62
  #
54
63
  # @param file_path [String] The path to the source draft file.
55
64
  # @param format [Symbol] The target output format (:html or :pdf).
@@ -75,9 +84,6 @@ module ReleaseHx
75
84
  # RHYML/YAML files: load as Release object and use Liquid templates
76
85
  release = load_rhyml_from_yaml(file_path, config: config)
77
86
  enrich_from_rhyml(release: release, config: config, format: format, outpath: outpath, force: true)
78
- when '.md'
79
- # Markdown files: use native converter
80
- convert_markdown(file_path, format: format, config: config, outpath: outpath, force: true)
81
87
  when '.adoc'
82
88
  # AsciiDoc files: use native converter
83
89
  convert_asciidoc(file_path, format: format, config: config, outpath: outpath, force: true)
@@ -86,70 +92,41 @@ module ReleaseHx
86
92
  end
87
93
  end
88
94
 
89
- # Converts Markdown files to rich-text formats using native converters.
95
+ # Converts AsciiDoc content or files to rich-text formats using configured engines.
90
96
  #
91
- # Uses Kramdown for HTML generation and Tilt with Pandoc for PDF conversion.
92
- # Note: Some parameters are reserved for API consistency across conversion methods.
93
- #
94
- # @param file_path [String] The path to the source Markdown file.
95
- # @param format [Symbol] The target output format (:html or :pdf).
96
- # @param config [ReleaseHx::Configuration] Reserved for future use.
97
- # @param outpath [String] The target output file path.
98
- # @param force [Boolean] Reserved for future use.
99
- # @return [String] The path to the generated output file.
100
- # rubocop:disable Lint/UnusedMethodArgument
101
- def self.convert_markdown file_path, format:, config:, outpath:, force:
102
- # NOTE: config and force parameters are currently unused but kept for API consistency
103
- content = File.read(file_path)
104
-
105
- case format.to_sym
106
- when :html
107
- require 'kramdown'
108
- enriched = Kramdown::Document.new(content).to_html
109
- WriteOps.safe_write(outpath, enriched)
110
- when :pdf
111
- # Use Tilt with Pandoc for direct Markdown to PDF conversion
112
- require 'tilt'
113
- require 'tilt/pandoc'
114
-
115
- template = Tilt::PandocTemplate.new(file_path)
116
- enriched = template.render(nil, to: 'pdf')
117
- WriteOps.safe_write(outpath, enriched)
118
- ReleaseHx.logger.info("PDF generated via Tilt/Pandoc: #{outpath}")
119
- else
120
- raise "Unsupported format for Markdown: #{format}"
121
- end
122
-
123
- outpath
124
- end
125
- # rubocop:enable Lint/UnusedMethodArgument
126
-
127
- # Converts AsciiDoc content or files to rich-text formats using Asciidoctor.
97
+ # Accepts either file paths or raw content strings as input. Selects appropriate
98
+ # converter engine based on config.conversions.engines.html/pdf settings with intelligent defaults:
99
+ # - AsciiDoc → HTML: asciidoctor-html5s (default)
100
+ # - AsciiDoc PDF: asciidoctor-pdf (default)
128
101
  #
129
- # Accepts either file paths or raw content strings as input. Uses Asciidoctor
130
- # for HTML generation and Asciidoctor-PDF for direct PDF output.
102
+ # HTML output can be wrapped with Bootstrap CSS when config.modes.html_wrap is enabled.
131
103
  #
132
104
  # @param file_path_or_content [String] File path or raw AsciiDoc content.
133
105
  # @param format [Symbol] The target output format (:html or :pdf).
134
106
  # @param outpath [String] The target output file path.
135
- # @param config [ReleaseHx::Configuration] Reserved for future use.
136
- # @param force [Boolean] Reserved for future use.
107
+ # @param config [ReleaseHx::Configuration] Configuration for engines and wrapping options.
108
+ # @param force [Boolean] Whether to overwrite existing files.
137
109
  # @return [String] The path to the generated output file.
138
- # rubocop:disable Lint/UnusedMethodArgument
139
110
  def self.convert_asciidoc file_path_or_content, format:, outpath:, config: nil, force: nil
140
- # NOTE: config and force parameters are currently unused but kept for API consistency
141
111
  # Determine if input is file path or raw content
142
112
  is_file = file_path_or_content.is_a?(String) && File.exist?(file_path_or_content)
143
113
  content = is_file ? File.read(file_path_or_content) : file_path_or_content
144
114
 
145
115
  case format.to_sym
146
116
  when :html
147
- require 'asciidoctor'
148
- enriched = Asciidoctor.convert(content, safe: :safe, backend: 'html5')
117
+ engine = resolve_engine(format: :html, source_format: :asciidoc, config: config)
118
+ html_fragment = convert_with_engine(content, engine: engine, format: :html)
119
+
120
+ # Wrap HTML with Bootstrap styling if enabled
121
+ enriched = if config && config.dig('modes', 'html_wrap') != false
122
+ ReleaseHx.logger.debug('Applying wrapper to AsciiDoc-derived HTML')
123
+ wrap_html(html_fragment, config)
124
+ else
125
+ html_fragment
126
+ end
149
127
  when :pdf
150
- require 'asciidoctor'
151
- require 'asciidoctor-pdf'
152
- Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'pdf')
128
+ engine = resolve_engine(format: :pdf, source_format: :asciidoc, config: config)
129
+ convert_with_engine(content, engine: engine, format: :pdf, outpath: outpath)
153
130
  return outpath
154
131
  else
155
132
  raise "Unsupported format for AsciiDoc: #{format}"
@@ -163,7 +140,6 @@ module ReleaseHx
163
140
  WriteOps.safe_write(outpath, enriched)
164
141
  outpath
165
142
  end
166
- # rubocop:enable Lint/UnusedMethodArgument
167
143
 
168
144
  # Loads RHYML data from a YAML file and creates a Release object.
169
145
  #
@@ -217,5 +193,135 @@ module ReleaseHx
217
193
  filename = SchemaGraphy::Templating.render_field_if_template(filename_template, context)
218
194
  File.join(output_dir, enrich_dir, filename.strip)
219
195
  end
196
+
197
+ # Wraps an HTML fragment with Bootstrap CSS and semantic HTML structure.
198
+ #
199
+ # Takes a raw HTML fragment and wraps it with proper DOCTYPE, head section with Bootstrap CDN link, and body tag.
200
+ # Uses the wrapper.html.liquid template for markup generation.
201
+ #
202
+ # @param html_fragment [String] The raw HTML content to wrap.
203
+ # @param config [ReleaseHx::Configuration] Configuration object for title and settings.
204
+ # @return [String] Complete HTML document with Bootstrap styling.
205
+ def self.wrap_html html_fragment, config
206
+ # Extract and parse framework setting
207
+ framework_spec = config.dig('history', 'html_framework') || 'bare'
208
+ framework_name, framework_version = parse_framework_spec(framework_spec)
209
+
210
+ template_path = WriteOps.resolve_template_path('wrapper.html.liquid', config)
211
+
212
+ template_vars = {
213
+ 'config' => config,
214
+ 'content' => html_fragment,
215
+ 'framework' => framework_name,
216
+ 'framework_version' => framework_version
217
+ }
218
+
219
+ WriteOps.process_template(template_path, template_vars, config)
220
+ end
221
+
222
+ # Parses framework specification string into name and version.
223
+ #
224
+ # @param spec [String] Framework specification like 'bootstrap5' or 'bootstrap:5.3.0'
225
+ # @return [Array<String, String>] Tuple of [framework_name, framework_version]
226
+ def self.parse_framework_spec spec
227
+ case spec.to_s
228
+ when /^(.+):(.+)$/
229
+ # Format: "bootstrap:5.3.0"
230
+ [::Regexp.last_match(1), ::Regexp.last_match(2)]
231
+ when 'bootstrap5'
232
+ ['bootstrap', '5.3.0']
233
+ when 'bootstrap4'
234
+ ['bootstrap', '4.6.2']
235
+ else
236
+ ['bare', nil]
237
+ end
238
+ end
239
+
240
+ # Resolves the appropriate conversion engine based on format and source.
241
+ #
242
+ # Applies intelligent defaults based on source format:
243
+ # - AsciiDoc → HTML: asciidoctor-html5s
244
+ # - AsciiDoc → PDF: asciidoctor-pdf
245
+ # - Markdown → HTML: kramdown
246
+ # - Markdown → PDF: pandoc
247
+ #
248
+ # @param format [Symbol] Output format (:html or :pdf)
249
+ # @param source_format [Symbol] Source format (:asciidoc or :markdown)
250
+ # @param config [Hash] Configuration hash
251
+ # @return [String] Engine name
252
+ def self.resolve_engine format:, source_format:, config: nil
253
+ # Check for explicit engine configuration
254
+ explicit_engine = config&.dig('conversions', 'engines', format.to_s)
255
+ return explicit_engine if explicit_engine
256
+
257
+ # Apply intelligent defaults based on source format
258
+ case [source_format, format]
259
+ when %i[asciidoc html]
260
+ 'asciidoctor-html5s'
261
+ when %i[asciidoc pdf]
262
+ 'asciidoctor-pdf'
263
+ when %i[markdown html]
264
+ 'kramdown'
265
+ when %i[markdown pdf]
266
+ 'pandoc'
267
+ else
268
+ raise "Unsupported conversion: #{source_format} → #{format}"
269
+ end
270
+ end
271
+
272
+ # Converts content using the specified engine.
273
+ #
274
+ # @param content [String] Source content to convert
275
+ # @param engine [String] Engine name (e.g., 'asciidoctor-html5s', 'asciidoctor-pdf')
276
+ # @param format [Symbol] Output format (:html or :pdf)
277
+ # @param outpath [String, nil] Output file path (required for PDF)
278
+ # @return [String] Converted content (for HTML) or output path (for PDF)
279
+ # rubocop:disable Lint/UnusedMethodArgument
280
+ def self.convert_with_engine content, engine:, format:, outpath: nil
281
+ case engine
282
+ when 'asciidoctor-html5', 'asciidoctor-html5s'
283
+ require 'asciidoctor'
284
+ backend = engine.split('-').last # 'html5' or 'html5s'
285
+
286
+ if backend == 'html5s'
287
+ begin
288
+ require 'asciidoctor-html5s'
289
+ ReleaseHx.logger.debug('Using asciidoctor-html5s backend for semantic HTML5')
290
+ rescue LoadError
291
+ ReleaseHx.logger.warn('asciidoctor-html5s not available, falling back to html5')
292
+ backend = 'html5'
293
+ end
294
+ end
295
+
296
+ Asciidoctor.convert(content, safe: :safe, backend: backend)
297
+
298
+ when 'asciidoctor-pdf'
299
+ require 'asciidoctor'
300
+ require 'asciidoctor-pdf'
301
+ ReleaseHx.logger.debug('Using asciidoctor-pdf for PDF generation')
302
+ Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'pdf')
303
+ outpath
304
+
305
+ when 'asciidoctor-web-pdf'
306
+ require 'asciidoctor'
307
+ require 'asciidoctor-web-pdf'
308
+ ReleaseHx.logger.debug('Using asciidoctor-web-pdf for PDF generation')
309
+ Asciidoctor.convert(content, to_file: outpath, safe: :safe, backend: 'web-pdf')
310
+ outpath
311
+
312
+ when 'kramdown'
313
+ require 'kramdown'
314
+ ReleaseHx.logger.debug('Using Kramdown for HTML conversion')
315
+ Kramdown::Document.new(content).to_html
316
+
317
+ when 'pandoc'
318
+ # Future implementation - would shell out to pandoc
319
+ raise 'Pandoc engine not yet implemented'
320
+
321
+ else
322
+ raise "Unsupported engine: #{engine}"
323
+ end
324
+ end
325
+ # rubocop:enable Lint/UnusedMethodArgument
220
326
  end
221
327
  end
@@ -53,7 +53,7 @@ module ReleaseHx
53
53
  releasehx_debug: true
54
54
  })
55
55
 
56
- raise "Template rendering failed:\n#{rendered}" if rendered.include?('Liquid error')
56
+ raise "Template rendering failed:\n#{rendered}" if rendered.match?(/Liquid error[:(]/)
57
57
 
58
58
  rendered
59
59
  end
@@ -6,9 +6,6 @@ require 'liquid'
6
6
  require 'erb'
7
7
  require 'yaml'
8
8
  require 'json'
9
- require_relative '../../schemagraphy'
10
- require_relative '../../schemagraphy/safe_expression'
11
- require_relative '../../sourcerer/jekyll'
12
9
 
13
10
  module ReleaseHx
14
11
  module RHYML
@@ -0,0 +1,15 @@
1
+ /* Bootstrap Overrides for ReleaseHx */
2
+ /* Users can override this file by placing bootstrap-overrides.css in their _templates directory */
3
+
4
+ /* Dark Mode Fixes */
5
+ @media (prefers-color-scheme: dark) {
6
+ /* Fix .bg-light in card headers - use semantic card background instead */
7
+ .card-header.bg-light {
8
+ background-color: var(--bs-card-cap-bg) !important;
9
+ border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);
10
+ }
11
+ }
12
+
13
+ .container-lg {
14
+ max-width: 900px;
15
+ }
@@ -11,4 +11,6 @@
11
11
  {%- assign item_count = 0 %}
12
12
  {%- embed section-text.liquid %}
13
13
 
14
+ [.changelog-section]
15
+
14
16
  {%- embed groupings.liquid %}
@@ -4,11 +4,13 @@
4
4
  {%- assign dflt_next_header_level = level | plus: 1 -%}
5
5
  {%- assign item_count = 0 %}
6
6
 
7
- <div class="changelog-section">
8
- {%- include header.liquid format=format level=level text=content_config.head %}
7
+ <section class="changelog-section py-4 px-3 mb-4 rounded">
8
+ {%- include header.liquid format=format level=level text=content_config.head %}
9
9
 
10
- {%- embed section-text.liquid %}
10
+ {%- embed section-text.liquid %}
11
11
 
12
+ <div class="changelog-content mt-4">
12
13
  {%- embed groupings.liquid %}
13
- </div>
14
+ </div>
15
+ </section>
14
16
 
@@ -1,3 +1,4 @@
1
+ <!-- .changelog-section -->
1
2
  {%- assign content_config = config.changelog %}
2
3
  {%- assign section_type = "changelog" %}
3
4
  {%- embed config-cascade.liquid %}
@@ -0,0 +1,263 @@
1
+ /* ReleaseHx Embedded CSS - {{ theme | capitalize }} Theme */
2
+
3
+ /* CSS Custom Properties - Theme: {{ theme }} */
4
+ :root {
5
+ {%- if theme == 'compact' %}
6
+ --release-spacing: 1.5rem;
7
+ --section-spacing: 1rem;
8
+ --item-spacing: 0.5rem;
9
+ --container-padding: 1rem;
10
+ --heading-margin: 0.75rem;
11
+ {%- elsif theme == 'detailed' %}
12
+ --release-spacing: 4rem;
13
+ --section-spacing: 3rem;
14
+ --item-spacing: 1.5rem;
15
+ --container-padding: 3rem;
16
+ --heading-margin: 2rem;
17
+ {%- else %}
18
+ --release-spacing: 3rem;
19
+ --section-spacing: 2.5rem;
20
+ --item-spacing: 1rem;
21
+ --container-padding: 2rem;
22
+ --heading-margin: 1.5rem;
23
+ {%- endif %}
24
+
25
+ /* Light Theme Colors */
26
+ --primary-color: #0d6efd;
27
+ --success-color: #198754;
28
+ --info-color: #0dcaf0;
29
+ --warning-color: #ffc107;
30
+ --danger-color: #dc3545;
31
+ --light-bg: #f8f9fa;
32
+ --white-bg: #ffffff;
33
+ --text-color: #212529;
34
+ --text-muted: #6c757d;
35
+ --border-color: #dee2e6;
36
+ --border-radius: 0.375rem;
37
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
38
+ --shadow-hover: 0 2px 8px rgba(0, 0, 0, 0.1);
39
+ }
40
+
41
+ /* Dark Theme Colors */
42
+ @media (prefers-color-scheme: dark) {
43
+ :root {
44
+ --primary-color: #6ea8fe;
45
+ --success-color: #75b798;
46
+ --info-color: #6edff6;
47
+ --warning-color: #ffda6a;
48
+ --danger-color: #ea868f;
49
+ --light-bg: #212529;
50
+ --white-bg: #2b3035;
51
+ --text-color: #ffffff;
52
+ --text-muted: #adb5bd;
53
+ --border-color: #495057;
54
+ --shadow: 0 1px 3px rgba(255, 255, 255, 0.1);
55
+ --shadow-hover: 0 2px 8px rgba(255, 255, 255, 0.15);
56
+ }
57
+ }
58
+
59
+ /* Manual Dark Theme Override */
60
+ .dark-theme {
61
+ --primary-color: #6ea8fe;
62
+ --success-color: #75b798;
63
+ --info-color: #6edff6;
64
+ --warning-color: #ffda6a;
65
+ --danger-color: #ea868f;
66
+ --light-bg: #212529;
67
+ --white-bg: #2b3035;
68
+ --text-color: #ffffff;
69
+ --text-muted: #adb5bd;
70
+ --border-color: #495057;
71
+ --shadow: 0 1px 3px rgba(255, 255, 255, 0.1);
72
+ --shadow-hover: 0 2px 8px rgba(255, 255, 255, 0.15);
73
+ }
74
+
75
+ /* Base Styles */
76
+ body {
77
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
78
+ line-height: 1.6;
79
+ color: var(--text-color);
80
+ background-color: var(--light-bg);
81
+ }
82
+
83
+ /* Container and Layout */
84
+ .release-history {
85
+ max-width: 1200px;
86
+ margin: 0 auto;
87
+ padding: var(--container-padding);
88
+ }
89
+
90
+ .release-section {
91
+ margin-bottom: var(--release-spacing);
92
+ padding: var(--container-padding);
93
+ background-color: var(--white-bg);
94
+ border-radius: var(--border-radius);
95
+ box-shadow: var(--shadow);
96
+ }
97
+
98
+ /* Section Styling */
99
+ .changelog-section, .notes-section {
100
+ margin-bottom: var(--section-spacing);
101
+ padding: var(--container-padding);
102
+ border-radius: var(--border-radius);
103
+ }
104
+
105
+ .changelog-section {
106
+ border-left: 4px solid var(--success-color);
107
+ background-color: rgba(25, 135, 84, 0.02);
108
+ }
109
+
110
+ .notes-section {
111
+ border-left: 4px solid var(--info-color);
112
+ background-color: rgba(13, 202, 240, 0.02);
113
+ }
114
+
115
+ /* Release Notes */
116
+ .release-note {
117
+ margin-bottom: var(--item-spacing);
118
+ transition: box-shadow 0.2s ease;
119
+ border: 1px solid var(--border-color);
120
+ border-radius: var(--border-radius);
121
+ }
122
+
123
+ .release-note:hover {
124
+ box-shadow: var(--shadow-hover);
125
+ border-color: var(--primary-color);
126
+ }
127
+
128
+ .card-header {
129
+ background-color: var(--light-bg);
130
+ border-bottom: 1px solid #dee2e6;
131
+ padding: 1rem 1.25rem;
132
+ }
133
+
134
+ .card-body {
135
+ padding: 1.25rem;
136
+ }
137
+
138
+ .card-footer {
139
+ background-color: var(--light-bg);
140
+ border-top: 1px solid #dee2e6;
141
+ padding: 0.75rem 1.25rem;
142
+ }
143
+
144
+ /* Changelog Entries */
145
+ .change-entry {
146
+ margin-bottom: var(--item-spacing);
147
+ padding: 0.75rem 1rem;
148
+ border: 1px solid var(--border-color);
149
+ border-radius: var(--border-radius);
150
+ transition: all 0.15s ease;
151
+ }
152
+
153
+ .change-entry:hover {
154
+ background-color: var(--light-bg);
155
+ border-color: var(--primary-color);
156
+ transform: translateX(4px);
157
+ }
158
+
159
+ /* Typography */
160
+ h1, h2, h3, h4, h5, h6 {
161
+ margin-bottom: var(--heading-margin);
162
+ line-height: 1.2;
163
+ }
164
+
165
+ {%- if theme == 'compact' %}
166
+ h1 { font-size: 1.75rem; }
167
+ h2 { font-size: 1.5rem; }
168
+ h3 { font-size: 1.25rem; }
169
+ {%- elsif theme == 'detailed' %}
170
+ h1 { font-size: 3rem; }
171
+ h2 { font-size: 2.5rem; }
172
+ h3 { font-size: 2rem; }
173
+ {%- endif %}
174
+
175
+ /* Badges and Labels */
176
+ .badge {
177
+ font-size: 0.75em;
178
+ padding: 0.35em 0.65em;
179
+ border-radius: 0.375rem;
180
+ font-weight: 600;
181
+ }
182
+
183
+ .badge-primary { background-color: var(--primary-color); color: white; }
184
+ .badge-success { background-color: var(--success-color); color: white; }
185
+ .badge-info { background-color: var(--info-color); color: white; }
186
+ .badge-warning { background-color: var(--warning-color); color: black; }
187
+ .badge-danger { background-color: var(--danger-color); color: white; }
188
+
189
+ /* Metadata Display */
190
+ .change-metadata {
191
+ display: flex;
192
+ flex-wrap: wrap;
193
+ gap: 0.5rem;
194
+ align-items: center;
195
+ }
196
+
197
+ .metadata-item {
198
+ display: inline-flex;
199
+ align-items: center;
200
+ gap: 0.25rem;
201
+ }
202
+
203
+ /* Links */
204
+ a {
205
+ color: var(--primary-color);
206
+ text-decoration: none;
207
+ }
208
+
209
+ a:hover {
210
+ text-decoration: underline;
211
+ }
212
+
213
+ /* Responsive Design */
214
+ @media (max-width: 768px) {
215
+ .release-history {
216
+ padding: 1rem;
217
+ }
218
+
219
+ .release-section {
220
+ padding: 1.5rem;
221
+ }
222
+
223
+ .change-entry {
224
+ padding: 0.5rem 0.75rem;
225
+ }
226
+
227
+ {%- if theme == 'detailed' %}
228
+ h1 { font-size: 2rem; }
229
+ h2 { font-size: 1.75rem; }
230
+ h3 { font-size: 1.5rem; }
231
+ {%- endif %}
232
+ }
233
+
234
+ /* Print Styles */
235
+ @media print {
236
+ body {
237
+ background: white;
238
+ color: black;
239
+ }
240
+
241
+ .release-section {
242
+ box-shadow: none;
243
+ border: 1px solid var(--border-color);
244
+ break-inside: avoid;
245
+ page-break-inside: avoid;
246
+ }
247
+
248
+ .release-note {
249
+ break-inside: avoid;
250
+ page-break-inside: avoid;
251
+ }
252
+
253
+ a {
254
+ color: black;
255
+ text-decoration: none;
256
+ }
257
+
258
+ a:after {
259
+ content: " (" attr(href) ")";
260
+ font-size: 0.8em;
261
+ color: #666;
262
+ }
263
+ }
@@ -15,6 +15,7 @@
15
15
  {%- if frame == "ordered" %}
16
16
  [start={{ item_count }}]
17
17
  {%- endif %}
18
+ [.change-entry]
18
19
  {{ bullet -}}
19
20
  {{ change.summ | trim }}
20
21
  {%- if change.note %} +
@@ -1,4 +1,3 @@
1
- {%- embed metadata-entry.html.liquid -%}
2
1
  {%- assign frame = frame %}
3
2
  {%- case frame %}
4
3
  {%- when "unordered" %}
@@ -10,29 +9,31 @@
10
9
  {%- assign item_class = "list-group-item border-0 px-0" %}
11
10
  {%- assign bullet = "" %}
12
11
  {%- else %}
13
- {%- assign list_class = "list-unstyled" %}
14
- {%- assign item_class = "mb-2" %}
12
+ {%- assign list_class = "list-group" %}
13
+ {%- assign item_class = "list-group-item px-3 py-2" %}
15
14
  {%- assign bullet = "" %}
16
15
  {%- endcase %}
17
16
 
18
17
  <div class="change-entry {{ item_class }}" id="entry-{{ change.chid }}">
19
- <div class="d-flex align-items-start">
20
- <div class="flex-grow-1">
21
- <span class="change-summary">{{ bullet }}{{ change.summ | trim | pasterize }}</span>
22
- {%- if change_metadata != "" -%}
23
- <div class="change-metadata mt-1">
24
- {{ change_metadata | trim }}
25
- </div>
26
- {%- endif %}
27
- </div>
28
- {%- if change.note %}
29
- <div class="note-link">
30
- <a href="#note-{{ change.chid }}" class="text-decoration-none">
31
- <i class="fa fa-sticky-note-o"></i>
32
- <small>NOTE</small>
33
- </a>
34
- </div>
35
- {%- endif %}
18
+ <div class="d-flex align-items-start justify-content-between">
19
+ <div class="flex-grow-1">
20
+ <p class="change-summary mb-2">
21
+ <span class="text-body">{{ bullet }}{{ change.summ | trim | pasterize }}</span>
22
+ </p>
23
+ {%- if change_metadata != "" -%}
24
+ <div class="change-metadata">
25
+ {{ change_metadata | trim }}
26
+ </div>
27
+ {%- endif %}
28
+ </div>
29
+ {%- if change.note %}
30
+ <div class="note-link ms-3 ps-2 text-nowrap">
31
+ <a href="#note-{{ change.chid }}" class="badge bg-warning text-decoration-none" title="Has a release note">
32
+ <i class="fa fa-sticky-note-o"></i>
33
+ <small>NOTE</small>
34
+ </a>
36
35
  </div>
36
+ {%- endif %}
37
+ </div>
37
38
  </div>
38
39