coradoc-html 1.1.7

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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/lib/coradoc/html/base.rb +157 -0
  4. data/lib/coradoc/html/config.rb +467 -0
  5. data/lib/coradoc/html/converter_base.rb +177 -0
  6. data/lib/coradoc/html/converters/admonition.rb +180 -0
  7. data/lib/coradoc/html/converters/attribute.rb +68 -0
  8. data/lib/coradoc/html/converters/attribute_reference.rb +60 -0
  9. data/lib/coradoc/html/converters/audio.rb +165 -0
  10. data/lib/coradoc/html/converters/base.rb +615 -0
  11. data/lib/coradoc/html/converters/bibliography.rb +82 -0
  12. data/lib/coradoc/html/converters/bibliography_entry.rb +108 -0
  13. data/lib/coradoc/html/converters/block_image.rb +72 -0
  14. data/lib/coradoc/html/converters/bold.rb +34 -0
  15. data/lib/coradoc/html/converters/break.rb +32 -0
  16. data/lib/coradoc/html/converters/comment_block.rb +42 -0
  17. data/lib/coradoc/html/converters/comment_line.rb +54 -0
  18. data/lib/coradoc/html/converters/cross_reference.rb +59 -0
  19. data/lib/coradoc/html/converters/document.rb +108 -0
  20. data/lib/coradoc/html/converters/example.rb +114 -0
  21. data/lib/coradoc/html/converters/highlight.rb +34 -0
  22. data/lib/coradoc/html/converters/include.rb +68 -0
  23. data/lib/coradoc/html/converters/inline_image.rb +41 -0
  24. data/lib/coradoc/html/converters/italic.rb +34 -0
  25. data/lib/coradoc/html/converters/line_break.rb +31 -0
  26. data/lib/coradoc/html/converters/link.rb +46 -0
  27. data/lib/coradoc/html/converters/list_item.rb +75 -0
  28. data/lib/coradoc/html/converters/listing.rb +99 -0
  29. data/lib/coradoc/html/converters/literal.rb +102 -0
  30. data/lib/coradoc/html/converters/monospace.rb +34 -0
  31. data/lib/coradoc/html/converters/open.rb +78 -0
  32. data/lib/coradoc/html/converters/ordered.rb +53 -0
  33. data/lib/coradoc/html/converters/paragraph.rb +46 -0
  34. data/lib/coradoc/html/converters/quote.rb +113 -0
  35. data/lib/coradoc/html/converters/reviewer_comment.rb +74 -0
  36. data/lib/coradoc/html/converters/reviewer_note.rb +134 -0
  37. data/lib/coradoc/html/converters/section.rb +90 -0
  38. data/lib/coradoc/html/converters/sidebar.rb +113 -0
  39. data/lib/coradoc/html/converters/source.rb +137 -0
  40. data/lib/coradoc/html/converters/source_code.rb +16 -0
  41. data/lib/coradoc/html/converters/span.rb +61 -0
  42. data/lib/coradoc/html/converters/strikethrough.rb +34 -0
  43. data/lib/coradoc/html/converters/subscript.rb +34 -0
  44. data/lib/coradoc/html/converters/superscript.rb +34 -0
  45. data/lib/coradoc/html/converters/table.rb +85 -0
  46. data/lib/coradoc/html/converters/table_cell.rb +203 -0
  47. data/lib/coradoc/html/converters/table_row.rb +45 -0
  48. data/lib/coradoc/html/converters/template_html_converter.rb +105 -0
  49. data/lib/coradoc/html/converters/term.rb +58 -0
  50. data/lib/coradoc/html/converters/text_element.rb +44 -0
  51. data/lib/coradoc/html/converters/underline.rb +34 -0
  52. data/lib/coradoc/html/converters/unordered.rb +47 -0
  53. data/lib/coradoc/html/converters/verse.rb +105 -0
  54. data/lib/coradoc/html/converters/video.rb +179 -0
  55. data/lib/coradoc/html/element_mapping.rb +210 -0
  56. data/lib/coradoc/html/entity.rb +137 -0
  57. data/lib/coradoc/html/input/cleaner.rb +163 -0
  58. data/lib/coradoc/html/input/config.rb +79 -0
  59. data/lib/coradoc/html/input/converters/a.rb +90 -0
  60. data/lib/coradoc/html/input/converters/aside.rb +23 -0
  61. data/lib/coradoc/html/input/converters/audio.rb +50 -0
  62. data/lib/coradoc/html/input/converters/base.rb +116 -0
  63. data/lib/coradoc/html/input/converters/blockquote.rb +25 -0
  64. data/lib/coradoc/html/input/converters/br.rb +19 -0
  65. data/lib/coradoc/html/input/converters/bypass.rb +83 -0
  66. data/lib/coradoc/html/input/converters/code.rb +25 -0
  67. data/lib/coradoc/html/input/converters/div.rb +25 -0
  68. data/lib/coradoc/html/input/converters/dl.rb +106 -0
  69. data/lib/coradoc/html/input/converters/drop.rb +28 -0
  70. data/lib/coradoc/html/input/converters/em.rb +23 -0
  71. data/lib/coradoc/html/input/converters/figure.rb +58 -0
  72. data/lib/coradoc/html/input/converters/h.rb +76 -0
  73. data/lib/coradoc/html/input/converters/head.rb +30 -0
  74. data/lib/coradoc/html/input/converters/hr.rb +20 -0
  75. data/lib/coradoc/html/input/converters/ignore.rb +22 -0
  76. data/lib/coradoc/html/input/converters/img.rb +110 -0
  77. data/lib/coradoc/html/input/converters/li.rb +35 -0
  78. data/lib/coradoc/html/input/converters/mark.rb +21 -0
  79. data/lib/coradoc/html/input/converters/markup.rb +107 -0
  80. data/lib/coradoc/html/input/converters/math.rb +46 -0
  81. data/lib/coradoc/html/input/converters/ol.rb +46 -0
  82. data/lib/coradoc/html/input/converters/p.rb +81 -0
  83. data/lib/coradoc/html/input/converters/pass_through.rb +19 -0
  84. data/lib/coradoc/html/input/converters/pre.rb +59 -0
  85. data/lib/coradoc/html/input/converters/q.rb +24 -0
  86. data/lib/coradoc/html/input/converters/strong.rb +22 -0
  87. data/lib/coradoc/html/input/converters/sub.rb +40 -0
  88. data/lib/coradoc/html/input/converters/sup.rb +40 -0
  89. data/lib/coradoc/html/input/converters/table.rb +64 -0
  90. data/lib/coradoc/html/input/converters/td.rb +70 -0
  91. data/lib/coradoc/html/input/converters/text.rb +67 -0
  92. data/lib/coradoc/html/input/converters/th.rb +20 -0
  93. data/lib/coradoc/html/input/converters/tr.rb +28 -0
  94. data/lib/coradoc/html/input/converters/video.rb +53 -0
  95. data/lib/coradoc/html/input/converters.rb +122 -0
  96. data/lib/coradoc/html/input/errors.rb +22 -0
  97. data/lib/coradoc/html/input/html_converter.rb +170 -0
  98. data/lib/coradoc/html/input/plugin.rb +169 -0
  99. data/lib/coradoc/html/input/plugins/plateau.rb +229 -0
  100. data/lib/coradoc/html/input/postprocessor.rb +31 -0
  101. data/lib/coradoc/html/input.rb +68 -0
  102. data/lib/coradoc/html/output.rb +95 -0
  103. data/lib/coradoc/html/renderer.rb +409 -0
  104. data/lib/coradoc/html/spa.rb +309 -0
  105. data/lib/coradoc/html/static.rb +293 -0
  106. data/lib/coradoc/html/template_config.rb +151 -0
  107. data/lib/coradoc/html/template_helpers.rb +58 -0
  108. data/lib/coradoc/html/template_locator.rb +114 -0
  109. data/lib/coradoc/html/theme/base.rb +231 -0
  110. data/lib/coradoc/html/theme/classic_renderer.rb +390 -0
  111. data/lib/coradoc/html/theme/modern/components/ui_components.rb +344 -0
  112. data/lib/coradoc/html/theme/modern/css_generator.rb +311 -0
  113. data/lib/coradoc/html/theme/modern/javascript_generator.rb +314 -0
  114. data/lib/coradoc/html/theme/modern/serializers/document_serializer.rb +382 -0
  115. data/lib/coradoc/html/theme/modern/tailwind_config_builder.rb +164 -0
  116. data/lib/coradoc/html/theme/modern/vue_template_generator.rb +374 -0
  117. data/lib/coradoc/html/theme/modern_renderer.rb +250 -0
  118. data/lib/coradoc/html/theme/registry.rb +153 -0
  119. data/lib/coradoc/html/theme.rb +13 -0
  120. data/lib/coradoc/html/transform/from_core_model.rb +32 -0
  121. data/lib/coradoc/html/transform/to_core_model.rb +39 -0
  122. data/lib/coradoc/html/version.rb +7 -0
  123. data/lib/coradoc/html.rb +255 -0
  124. metadata +264 -0
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Theme
6
+ class ModernRenderer
7
+ # Build Tailwind CSS configuration
8
+ module TailwindConfigBuilder
9
+ class << self
10
+ # Build Tailwind configuration
11
+ #
12
+ # @param config [Hash] Theme configuration
13
+ # @return [String] Tailwind configuration script
14
+ def build(config)
15
+ primary = config[:primary_color] || '#6366f1'
16
+ accent = config[:accent_color] || '#8b5cf6'
17
+
18
+ # Parse hex colors to RGB for opacity variants
19
+ primary_rgb = hex_to_rgb(primary)
20
+ accent_rgb = hex_to_rgb(accent)
21
+
22
+ <<~JS
23
+ tailwind.config = {
24
+ darkMode: 'class',
25
+ theme: {
26
+ extend: {
27
+ colors: {
28
+ primary: {
29
+ DEFAULT: '#{primary}',
30
+ rgb: '#{primary_rgb}',
31
+ 50: '#{adjust_color(primary, 40)}',
32
+ 100: '#{adjust_color(primary, 30)}',
33
+ 200: '#{adjust_color(primary, 20)}',
34
+ 300: '#{adjust_color(primary, 10)}',
35
+ 400: '#{adjust_color(primary, 5)}',
36
+ 500: '#{primary}',
37
+ 600: '#{adjust_color(primary, -5)}',
38
+ 700: '#{adjust_color(primary, -10)}',
39
+ 800: '#{adjust_color(primary, -20)}',
40
+ 900: '#{adjust_color(primary, -30)}',
41
+ },
42
+ accent: {
43
+ DEFAULT: '#{accent}',
44
+ rgb: '#{accent_rgb}',
45
+ 50: '#{adjust_color(accent, 40)}',
46
+ 100: '#{adjust_color(accent, 30)}',
47
+ 200: '#{adjust_color(accent, 20)}',
48
+ 300: '#{adjust_color(accent, 10)}',
49
+ 400: '#{adjust_color(accent, 5)}',
50
+ 500: '#{accent}',
51
+ 600: '#{adjust_color(accent, -5)}',
52
+ 700: '#{adjust_color(accent, -10)}',
53
+ 800: '#{adjust_color(accent, -20)}',
54
+ 900: '#{adjust_color(accent, -30)}',
55
+ },
56
+ },
57
+ fontFamily: {
58
+ sans: [
59
+ 'system-ui',
60
+ '-apple-system',
61
+ 'BlinkMacSystemFont',
62
+ 'Segoe UI',
63
+ 'Roboto',
64
+ 'sans-serif',
65
+ ],
66
+ mono: [
67
+ 'ui-monospace',
68
+ 'SFMono-Regular',
69
+ 'Menlo',
70
+ 'Monaco',
71
+ 'Consolas',
72
+ 'monospace',
73
+ ],
74
+ },
75
+ maxWidth: {
76
+ 'content': '#{config[:content_width] || '65ch'}',
77
+ 'sidebar': '#{config[:sidebar_width] || '280px'}',
78
+ },
79
+ animation: {
80
+ 'fade-in': 'fadeIn #{config[:animation_duration] || '300ms'} ease-out',
81
+ 'slide-up': 'slideUp #{config[:animation_duration] || '300ms'} ease-out',
82
+ 'slide-down': 'slideDown #{config[:animation_duration] || '300ms'} ease-out',
83
+ 'scale-in': 'scaleIn #{config[:animation_duration] || '300ms'} ease-out',
84
+ },
85
+ keyframes: {
86
+ fadeIn: {
87
+ '0%': { opacity: '0' },
88
+ '100%': { opacity: '1' },
89
+ },
90
+ slideUp: {
91
+ '0%': { transform: 'translateY(10px)', opacity: '0' },
92
+ '100%': { transform: 'translateY(0)', opacity: '1' },
93
+ },
94
+ slideDown: {
95
+ '0%': { transform: 'translateY(-10px)', opacity: '0' },
96
+ '100%': { transform: 'translateY(0)', opacity: '1' },
97
+ },
98
+ scaleIn: {
99
+ '0%': { transform: 'scale(0.95)', opacity: '0' },
100
+ '100%': { transform: 'scale(1)', opacity: '1' },
101
+ },
102
+ },
103
+ },
104
+ },
105
+ }
106
+ JS
107
+ end
108
+
109
+ private
110
+
111
+ # Convert hex color to RGB format
112
+ #
113
+ # @param hex [String] Hex color code
114
+ # @return [String] RGB format
115
+ def hex_to_rgb(hex)
116
+ hex = hex.delete('#')
117
+ case hex.length
118
+ when 3
119
+ r, g, b = hex.chars.map { |c| "#{c}#{c}".hex }
120
+ when 6
121
+ r = hex[0..1].hex
122
+ g = hex[2..3].hex
123
+ b = hex[4..5].hex
124
+ else
125
+ return '99, 102, 241' # Default to indigo-500
126
+ end
127
+ "#{r}, #{g}, #{b}"
128
+ end
129
+
130
+ # Adjust color lightness
131
+ #
132
+ # @param hex [String] Hex color code
133
+ # @param amount [Integer] Amount to adjust (-100 to 100)
134
+ # @return [String] Adjusted hex color
135
+ def adjust_color(hex, amount)
136
+ # Simple color adjustment
137
+ hex = hex.delete('#')
138
+ r = hex[0..1].hex
139
+ g = hex[2..3].hex
140
+ b = hex[4..5].hex
141
+
142
+ amount = amount.to_i
143
+ if amount.positive?
144
+ # Lighten
145
+ factor = 1 + (amount / 100.0)
146
+ r = [(r * factor).round, 255].min
147
+ g = [(g * factor).round, 255].min
148
+ b = [(b * factor).round, 255].min
149
+ elsif amount.negative?
150
+ # Darken
151
+ factor = 1 - (amount.abs / 100.0)
152
+ r = [(r * factor).round, 0].max
153
+ g = [(g * factor).round, 0].max
154
+ b = [(b * factor).round, 0].max
155
+ end
156
+
157
+ format('%02x%02x%02x', r, g, b)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,374 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Html
5
+ module Theme
6
+ class ModernRenderer
7
+ # Vue component templates for rendering document elements
8
+ module VueTemplates
9
+ class << self
10
+ # Generate Vue component template for a given element type
11
+ #
12
+ # @param type [String] Element type
13
+ # @return [String] Vue component template
14
+ def template_for(type)
15
+ case type
16
+ when 'document' then document_template
17
+ when 'section' then section_template
18
+ when 'paragraph' then paragraph_template
19
+ when 'admonition' then admonition_template
20
+ when 'list' then list_template
21
+ when 'block' then block_template
22
+ when 'table' then table_template
23
+ when 'image' then image_template
24
+ when 'link' then link_template
25
+ when 'xref' then cross_reference_template
26
+ else
27
+ generic_template
28
+ end
29
+ end
30
+
31
+ # Document component template
32
+ #
33
+ # @return [String] Document template
34
+ def document_template
35
+ <<~VUE
36
+ <div class="document-wrapper">
37
+ <header v-if="document.header" class="document-header mb-8">
38
+ <h1 class="text-4xl font-bold text-gray-900 dark:text-white">
39
+ {{ document.header.title?.text || document.title || 'Untitled Document' }}
40
+ </h1>
41
+ <p v-if="document.header?.author" class="text-gray-600 dark:text-gray-400 mt-2">
42
+ {{ document.header.author }}
43
+ </p>
44
+ </header>
45
+
46
+ <div class="toc-toggle mb-4 flex justify-end">
47
+ <button
48
+ @click="tocCollapsed = !tocCollapsed"
49
+ class="px-4 py-2 rounded-lg bg-white dark:bg-gray-800 shadow-md hover:shadow-lg transition-all flex items-center gap-2"
50
+ >
51
+ <svg class="w-5 h-5" :class="{ 'rotate-180': !tocCollapsed }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
53
+ </svg>
54
+ <span>{{ tocCollapsed ? 'Show' : 'Hide' }} Contents</span>
55
+ </button>
56
+ </div>
57
+
58
+ <div class="flex gap-8">
59
+ <!-- TOC Sidebar -->
60
+ <aside v-if="showToc" class="toc-sidebar" :class="{ 'collapsed': tocCollapsed }">
61
+ <nav class="toc-nav">
62
+ <h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Contents</h3>
63
+ <ul class="space-y-1">
64
+ <li
65
+ v-for="item in tocItems"
66
+ :key="item.id"
67
+ @click="scrollToSection(item.id)"
68
+ class="toc-item"
69
+ :class="{ 'active': activeSection === item.id }"
70
+ :style="{ paddingLeft: (item.level * 0.75) + 'rem' }"
71
+ >
72
+ {{ item.title }}
73
+ </li>
74
+ </ul>
75
+ </nav>
76
+ </aside>
77
+
78
+ <!-- Main Content -->
79
+ <main class="flex-1 min-w-0">
80
+ <article class="prose prose-lg dark:prose-invert max-w-none">
81
+ <template v-for="(section, index) in document.sections" :key="section.id || index">
82
+ <component :is="'section-' + section.type" :data="section" v-if="section.type === 'section'" />
83
+ <component :is="'element-' + section.type" :data="section" v-else />
84
+ </template>
85
+ </article>
86
+ </main>
87
+ </div>
88
+ </div>
89
+ VUE
90
+ end
91
+
92
+ # Section component template
93
+ #
94
+ # @return [String] Section template
95
+ def section_template
96
+ <<~VUE
97
+ <section :id="data.id" class="section scroll-mt-20">
98
+ <h1 v-if="data.level === 1" class="section-title text-3xl font-bold mb-4">{{ data.title?.text || data.title }}</h1>
99
+ <h2 v-else-if="data.level === 2" class="section-title text-2xl font-semibold mb-3">{{ data.title?.text || data.title }}</h2>
100
+ <h3 v-else-if="data.level === 3" class="section-title text-xl font-semibold mb-2">{{ data.title?.text || data.title }}</h3>
101
+ <h4 v-else-if="data.level === 4" class="section-title text-lg font-semibold mb-2">{{ data.title?.text || data.title }}</h4>
102
+ <h5 v-else-if="data.level === 5" class="section-title text-base font-semibold mb-1">{{ data.title?.text || data.title }}</h5>
103
+ <h2 v-else class="section-title text-2xl font-semibold mb-3">{{ data.title?.text || data.title }}</h2>
104
+
105
+ <div class="section-content">
106
+ <template v-for="(item, index) in data.content" :key="item.id || index">
107
+ <component :is="'element-' + item.type" :data="item" />
108
+ </template>
109
+
110
+ <template v-if="data.sections && data.sections.length > 0">
111
+ <template v-for="(subsection, index) in data.sections" :key="subsection.id || index">
112
+ <component :is="'section-section'" :data="subsection" />
113
+ </template>
114
+ </template>
115
+ </div>
116
+ </section>
117
+ VUE
118
+ end
119
+
120
+ # Paragraph component template
121
+ #
122
+ # @return [String] Paragraph template
123
+ def paragraph_template
124
+ <<~VUE
125
+ <p :id="data.id" class="paragraph leading-relaxed">
126
+ <template v-for="(item, index) in data.content" :key="item.id || index">
127
+ <component :is="'inline-' + item.type" v-if="item.type" :data="item" />
128
+ <span v-else>{{ item.content || item }}</span>
129
+ </template>
130
+ </p>
131
+ VUE
132
+ end
133
+
134
+ # Admonition component template
135
+ #
136
+ # @return [String] Admonition template
137
+ def admonition_template
138
+ <<~VUE
139
+ <div :id="data.id" :class="['admonition', 'admonition-' + data.style.toLowerCase()]">
140
+ <div class="admonition-title font-semibold mb-2 flex items-center gap-2">
141
+ <span class="admonition-icon">{{ admonitionIcon(data.style) }}</span>
142
+ <span>{{ data.title || admonitionTitle(data.style) }}</span>
143
+ </div>
144
+ <div class="admonition-content">
145
+ <template v-for="(item, index) in data.content" :key="item.id || index">
146
+ <component :is="'element-' + item.type" :data="item" />
147
+ </template>
148
+ </div>
149
+ </div>
150
+ VUE
151
+ end
152
+
153
+ # List component template
154
+ #
155
+ # @return [String] List template
156
+ def list_template
157
+ <<~VUE
158
+ <div :id="data.id" class="list-wrapper">
159
+ <ul v-if="data.list_type === 'unordered'" class="list-disc list-inside space-y-1">
160
+ <li v-for="(item, index) in data.items" :key="item.id || index" class="list-item">
161
+ <template v-for="(content, idx) in item.content" :key="content.id || idx">
162
+ <component :is="'element-' + content.type" :data="content" />
163
+ </template>
164
+ </li>
165
+ </ul>
166
+
167
+ <ol v-else-if="data.list_type === 'ordered'" class="list-decimal list-inside space-y-1">
168
+ <li v-for="(item, index) in data.items" :key="item.id || index" class="list-item">
169
+ <template v-for="(content, idx) in item.content" :key="content.id || idx">
170
+ <component :is="'element-' + content.type" :data="content" />
171
+ </template>
172
+ </li>
173
+ </ol>
174
+
175
+ <dl v-else-if="data.list_type === 'definition'" class="space-y-2">
176
+ <template v-for="(item, index) in data.items" :key="item.id || index">
177
+ <div class="definition-item">
178
+ <dt class="font-semibold">{{ item.terms?.join(', ') }}</dt>
179
+ <dd class="ml-4">
180
+ <template v-for="(content, idx) in item.content" :key="content.id || idx">
181
+ <component :is="'element-' + content.type" :data="content" />
182
+ </template>
183
+ </dd>
184
+ </div>
185
+ </template>
186
+ </dl>
187
+ </div>
188
+ VUE
189
+ end
190
+
191
+ # Block component template (listing, literal, example, quote)
192
+ #
193
+ # @return [String] Block template
194
+ def block_template
195
+ <<~VUE
196
+ <div :id="data.id" :class="['block', 'block-' + data.block_type.toLowerCase()]">
197
+ <div v-if="data.title" class="block-title font-semibold mb-2">{{ data.title }}</div>
198
+
199
+ <div v-if="data.block_type === 'listing' || data.block_type === 'literal'" class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4 overflow-x-auto">
200
+ <pre class="whitespace-pre-wrap">{{ blockContent(data) }}</pre>
201
+ <button
202
+ @click="copyCode(blockContent(data), $event)"
203
+ class="copy-code-button"
204
+ >
205
+ Copy
206
+ </button>
207
+ </div>
208
+
209
+ <blockquote v-else-if="data.block_type === 'quote'" class="border-l-4 border-primary-500 pl-4 italic">
210
+ <template v-for="(item, index) in data.content" :key="item.id || index">
211
+ <component :is="'element-' + item.type" :data="item" />
212
+ </template>
213
+ </blockquote>
214
+
215
+ <div v-else class="block-content">
216
+ <template v-for="(item, index) in data.content" :key="item.id || index">
217
+ <component :is="'element-' + item.type" :data="item" />
218
+ </template>
219
+ </div>
220
+ </div>
221
+ VUE
222
+ end
223
+
224
+ # Table component template
225
+ #
226
+ # @return [String] Table template
227
+ def table_template
228
+ <<~VUE
229
+ <div :id="data.id" class="table-wrapper overflow-x-auto my-4">
230
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
231
+ <caption v-if="data.caption" class="caption-bottom text-sm text-gray-600 dark:text-gray-400 py-2">
232
+ {{ data.caption }}
233
+ </caption>
234
+
235
+ <thead v-if="data.header && data.header.length > 0" class="bg-gray-50 dark:bg-gray-800">
236
+ <tr>
237
+ <th
238
+ v-for="(cell, index) in data.header[0].cells"
239
+ :key="cell.id || index"
240
+ class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
241
+ :colspan="cell.colspan"
242
+ :rowspan="cell.rowspan"
243
+ >
244
+ {{ cellContent(cell) }}
245
+ </th>
246
+ </tr>
247
+ </thead>
248
+
249
+ <tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
250
+ <tr v-for="(row, rowIndex) in data.body" :key="row.id || rowIndex" class="hover:bg-gray-50 dark:hover:bg-gray-800">
251
+ <td
252
+ v-for="(cell, cellIndex) in row.cells"
253
+ :key="cell.id || cellIndex"
254
+ class="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100"
255
+ :colspan="cell.colspan"
256
+ :rowspan="cell.rowspan"
257
+ >
258
+ <template v-for="(item, index) in cell.content" :key="item.id || index">
259
+ <component :is="'element-' + item.type" :data="item" />
260
+ </template>
261
+ </td>
262
+ </tr>
263
+ </tbody>
264
+ </table>
265
+ </div>
266
+ VUE
267
+ end
268
+
269
+ # Image component template
270
+ #
271
+ # @return [String] Image template
272
+ def image_template
273
+ <<~VUE
274
+ <figure :id="data.id" :class="data.inline ? 'inline-image' : 'block-image'" class="my-4">
275
+ <img
276
+ :src="data.src"
277
+ :alt="data.alt || ''"
278
+ :title="data.title"
279
+ :width="data.width"
280
+ :height="data.height"
281
+ :class="data.inline ? 'inline max-h-6 align-middle' : 'w-full rounded-lg shadow-lg'"
282
+ />
283
+ <figcaption v-if="data.title" class="text-center text-sm text-gray-600 dark:text-gray-400 mt-2">
284
+ {{ data.title }}
285
+ </figcaption>
286
+ </figure>
287
+ VUE
288
+ end
289
+
290
+ # Link component template
291
+ #
292
+ # @return [String] Link template
293
+ def link_template
294
+ <<~VUE
295
+ <a
296
+ :href="data.href"
297
+ :target="data.target || '_blank'"
298
+ :rel="data.target === '_blank' ? 'noopener noreferrer' : null"
299
+ class="text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-300 underline"
300
+ >
301
+ <template v-for="(item, index) in data.content" :key="item.id || index">
302
+ <component :is="'inline-' + item.type" v-if="item.type" :data="item" />
303
+ <span v-else>{{ item.content || item }}</span>
304
+ </template>
305
+ </a>
306
+ VUE
307
+ end
308
+
309
+ # Cross reference component template
310
+ #
311
+ # @return [String] Cross reference template
312
+ def cross_reference_template
313
+ <<~VUE
314
+ <a
315
+ :href="'#' + data.target"
316
+ @click.prevent="scrollToSection(data.target)"
317
+ class="text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-300 underline cursor-pointer"
318
+ >
319
+ <template v-for="(item, index) in data.content" :key="item.id || index">
320
+ <component :is="'inline-' + item.type" v-if="item.type" :data="item" />
321
+ <span v-else>{{ item.content || item }}</span>
322
+ </template>
323
+ </a>
324
+ VUE
325
+ end
326
+
327
+ # Generic component template (fallback)
328
+ #
329
+ # @return [String] Generic template
330
+ def generic_template
331
+ <<~VUE
332
+ <div :id="data.id" :class="['element', 'element-' + data.type]">
333
+ <template v-if="data.content">
334
+ <template v-for="(item, index) in data.content" :key="item.id || index">
335
+ <component :is="'element-' + item.type" :data="item" v-if="item.type" />
336
+ <span v-else>{{ item.content || item }}</span>
337
+ </template>
338
+ </template>
339
+ <span v-else>{{ data.content || data }}</span>
340
+ </div>
341
+ VUE
342
+ end
343
+
344
+ private
345
+
346
+ # Get admonition icon
347
+ #
348
+ # @param style [String] Admonition style
349
+ # @return [String] Icon emoji
350
+ def admonition_icon(style)
351
+ case style&.downcase
352
+ when 'note' then '📝'
353
+ when 'tip' then '💡'
354
+ when 'warning' then '⚠️'
355
+ when 'caution' then '🔥'
356
+ when 'important' then '❗'
357
+ else
358
+ 'ℹ️'
359
+ end
360
+ end
361
+
362
+ # Get admonition title
363
+ #
364
+ # @param style [String] Admonition style
365
+ # @return [String] Title text
366
+ def admonition_title(style)
367
+ style&.capitalize || 'Note'
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
374
+ end