xlsxrb 0.1.11 → 0.1.13

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -34
  3. data/README.md +26 -21
  4. data/lib/xlsxrb/chart_builder.rb +18 -18
  5. data/lib/xlsxrb/dsl_helpers.rb +175 -0
  6. data/lib/xlsxrb/elements/cell.rb +56 -22
  7. data/lib/xlsxrb/elements/column.rb +27 -3
  8. data/lib/xlsxrb/elements/row.rb +27 -8
  9. data/lib/xlsxrb/elements/workbook.rb +27 -14
  10. data/lib/xlsxrb/elements/worksheet.rb +17 -1
  11. data/lib/xlsxrb/ooxml/cfb.rb +9 -6
  12. data/lib/xlsxrb/ooxml/crypto/agile.rb +5 -5
  13. data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +784 -0
  14. data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +3478 -0
  15. data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +1796 -0
  16. data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +428 -0
  17. data/lib/xlsxrb/ooxml/reader/listeners.rb +4 -6446
  18. data/lib/xlsxrb/ooxml/shared_strings_parser.rb +3 -5
  19. data/lib/xlsxrb/ooxml/utils.rb +5 -6
  20. data/lib/xlsxrb/ooxml/workbook_writer.rb +5 -7
  21. data/lib/xlsxrb/ooxml/worksheet_parser.rb +172 -9
  22. data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +1602 -0
  23. data/lib/xlsxrb/ooxml/writer/features_xml.rb +731 -0
  24. data/lib/xlsxrb/ooxml/writer/styles_xml.rb +348 -0
  25. data/lib/xlsxrb/ooxml/writer.rb +9 -2646
  26. data/lib/xlsxrb/ooxml/xml_builder.rb +31 -2
  27. data/lib/xlsxrb/ooxml/zip_generator.rb +26 -5
  28. data/lib/xlsxrb/ooxml/zip_reader.rb +364 -78
  29. data/lib/xlsxrb/ooxml/zip_writer.rb +32 -5
  30. data/lib/xlsxrb/stream_sheet.rb +94 -6
  31. data/lib/xlsxrb/stream_writer.rb +48 -115
  32. data/lib/xlsxrb/style_builder.rb +10 -1
  33. data/lib/xlsxrb/version.rb +1 -1
  34. data/lib/xlsxrb/workbook_builder.rb +1 -1
  35. data/lib/xlsxrb/worksheet_builder.rb +19 -81
  36. data/lib/xlsxrb.rb +449 -171
  37. data/sig/generated/xlsxrb/chart_builder.rbs +36 -36
  38. data/sig/generated/xlsxrb/dsl_helpers.rbs +92 -0
  39. data/sig/generated/xlsxrb/elements/cell.rbs +29 -0
  40. data/sig/generated/xlsxrb/elements/column.rbs +32 -21
  41. data/sig/generated/xlsxrb/elements/row.rbs +94 -14
  42. data/sig/generated/xlsxrb/elements/workbook.rbs +102 -10
  43. data/sig/generated/xlsxrb/elements/worksheet.rbs +9 -0
  44. data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +173 -0
  45. data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +400 -0
  46. data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +442 -0
  47. data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +80 -0
  48. data/sig/generated/xlsxrb/ooxml/reader/listeners.rbs +0 -1072
  49. data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +0 -2
  50. data/sig/generated/xlsxrb/ooxml/utils.rbs +2 -0
  51. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +5 -4
  52. data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +79 -0
  53. data/sig/generated/xlsxrb/ooxml/writer/features_xml.rbs +83 -0
  54. data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +53 -0
  55. data/sig/generated/xlsxrb/ooxml/writer.rbs +6 -185
  56. data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +18 -0
  57. data/sig/generated/xlsxrb/ooxml/zip_generator.rbs +15 -0
  58. data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +96 -8
  59. data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +5 -1
  60. data/sig/generated/xlsxrb/stream_sheet.rbs +43 -3
  61. data/sig/generated/xlsxrb/style_builder.rbs +7 -0
  62. data/sig/generated/xlsxrb.rbs +18 -11
  63. metadata +22 -144
  64. data/.devcontainer/Dockerfile +0 -65
  65. data/.devcontainer/devcontainer.json +0 -17
  66. data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +0 -9
  67. data/.gem_rbs_collection/ast/2.4/ast.rbs +0 -73
  68. data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +0 -9
  69. data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +0 -4
  70. data/.gem_rbs_collection/concurrent-ruby/1.1/atomic_reference.rbs +0 -16
  71. data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +0 -96
  72. data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +0 -4
  73. data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +0 -68
  74. data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +0 -249
  75. data/.gem_rbs_collection/concurrent-ruby/1.1/set.rbs +0 -4
  76. data/.gem_rbs_collection/concurrent-ruby/1.1/timer_task.rbs +0 -47
  77. data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +0 -5
  78. data/.gem_rbs_collection/csv/3.3/.rbs_meta.yaml +0 -9
  79. data/.gem_rbs_collection/csv/3.3/csv.rbs +0 -3871
  80. data/.gem_rbs_collection/csv/3.3/manifest.yaml +0 -3
  81. data/.gem_rbs_collection/lint_roller/1.1/.rbs_meta.yaml +0 -9
  82. data/.gem_rbs_collection/lint_roller/1.1/lint_roller.rbs +0 -48
  83. data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +0 -9
  84. data/.gem_rbs_collection/listen/3.9/listen.rbs +0 -25
  85. data/.gem_rbs_collection/listen/3.9/listener.rbs +0 -24
  86. data/.gem_rbs_collection/logger/1.7/.rbs_meta.yaml +0 -9
  87. data/.gem_rbs_collection/logger/1.7/formatter.rbs +0 -45
  88. data/.gem_rbs_collection/logger/1.7/log_device.rbs +0 -100
  89. data/.gem_rbs_collection/logger/1.7/logger.rbs +0 -796
  90. data/.gem_rbs_collection/logger/1.7/manifest.yaml +0 -2
  91. data/.gem_rbs_collection/logger/1.7/period.rbs +0 -17
  92. data/.gem_rbs_collection/logger/1.7/severity.rbs +0 -34
  93. data/.gem_rbs_collection/nokogiri/1.11/.rbs_meta.yaml +0 -9
  94. data/.gem_rbs_collection/nokogiri/1.11/nokogiri.rbs +0 -2332
  95. data/.gem_rbs_collection/nokogiri/1.11/patch.rbs +0 -4
  96. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +0 -9
  97. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +0 -86
  98. data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +0 -9
  99. data/.gem_rbs_collection/parser/3.2/manifest.yaml +0 -7
  100. data/.gem_rbs_collection/parser/3.2/parser.rbs +0 -194
  101. data/.gem_rbs_collection/parser/3.2/polyfill.rbs +0 -4
  102. data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +0 -9
  103. data/.gem_rbs_collection/rainbow/3.0/global.rbs +0 -7
  104. data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +0 -209
  105. data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +0 -5
  106. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +0 -9
  107. data/.gem_rbs_collection/rake/13.0/manifest.yaml +0 -2
  108. data/.gem_rbs_collection/rake/13.0/rake.rbs +0 -39
  109. data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +0 -9
  110. data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +0 -17
  111. data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +0 -9
  112. data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +0 -208
  113. data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +0 -9
  114. data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +0 -903
  115. data/.gem_rbs_collection/rubyzip/3.2/.rbs_meta.yaml +0 -9
  116. data/.gem_rbs_collection/rubyzip/3.2/manifest.yaml +0 -8
  117. data/.gem_rbs_collection/rubyzip/3.2/zip/central_directory.rbs +0 -42
  118. data/.gem_rbs_collection/rubyzip/3.2/zip/compressor.rbs +0 -5
  119. data/.gem_rbs_collection/rubyzip/3.2/zip/constants.rbs +0 -47
  120. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/aes_encryption.rbs +0 -30
  121. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/decrypted_io.rbs +0 -9
  122. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/encryption.rbs +0 -7
  123. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/null_encryption.rbs +0 -19
  124. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/traditional_encryption.rbs +0 -31
  125. data/.gem_rbs_collection/rubyzip/3.2/zip/decompressor.rbs +0 -18
  126. data/.gem_rbs_collection/rubyzip/3.2/zip/deflater.rbs +0 -12
  127. data/.gem_rbs_collection/rubyzip/3.2/zip/dirtyable.rbs +0 -11
  128. data/.gem_rbs_collection/rubyzip/3.2/zip/dos_time.rbs +0 -13
  129. data/.gem_rbs_collection/rubyzip/3.2/zip/entry.rbs +0 -95
  130. data/.gem_rbs_collection/rubyzip/3.2/zip/entry_set.rbs +0 -31
  131. data/.gem_rbs_collection/rubyzip/3.2/zip/errors.rbs +0 -58
  132. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/aes.rbs +0 -24
  133. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/generic.rbs +0 -17
  134. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/ntfs.rbs +0 -23
  135. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/old_unix.rbs +0 -22
  136. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/universal_time.rbs +0 -30
  137. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unix.rbs +0 -20
  138. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unknown.rbs +0 -15
  139. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/zip64.rbs +0 -26
  140. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field.rbs +0 -21
  141. data/.gem_rbs_collection/rubyzip/3.2/zip/file.rbs +0 -131
  142. data/.gem_rbs_collection/rubyzip/3.2/zip/file_split.rbs +0 -14
  143. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/dir.rbs +0 -33
  144. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/directory_iterator.rbs +0 -21
  145. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file.rbs +0 -63
  146. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file_stat.rbs +0 -55
  147. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/zip_file_name_mapper.rbs +0 -35
  148. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem.rbs +0 -7
  149. data/.gem_rbs_collection/rubyzip/3.2/zip/inflater.rbs +0 -10
  150. data/.gem_rbs_collection/rubyzip/3.2/zip/input_stream.rbs +0 -22
  151. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_input_stream.rbs +0 -29
  152. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_output_stream.rbs +0 -17
  153. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras.rbs +0 -13
  154. data/.gem_rbs_collection/rubyzip/3.2/zip/null_compressor.rbs +0 -10
  155. data/.gem_rbs_collection/rubyzip/3.2/zip/null_decompressor.rbs +0 -8
  156. data/.gem_rbs_collection/rubyzip/3.2/zip/null_input_stream.rbs +0 -6
  157. data/.gem_rbs_collection/rubyzip/3.2/zip/output_stream.rbs +0 -30
  158. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_compressor.rbs +0 -10
  159. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_decompressor.rbs +0 -10
  160. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_directory.rbs +0 -5
  161. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_stream.rbs +0 -15
  162. data/.gem_rbs_collection/rubyzip/3.2/zip/version.rbs +0 -3
  163. data/.gem_rbs_collection/rubyzip/3.2/zip.rbs +0 -40
  164. data/Rakefile +0 -567
  165. data/Steepfile +0 -18
  166. data/benchmark.rb +0 -406
  167. data/docs/ARCHITECTURE.md +0 -514
  168. data/docs/DEVELOPMENT.md +0 -133
  169. data/docs/PEER_LIBRARIES.md +0 -121
  170. data/docs/QUALITY_ASSURANCE.md +0 -25
  171. data/docs/SPEC_SOURCES.md +0 -48
  172. data/docs/assets/benchmark_results.svg +0 -144
  173. data/docs/assets/lsp_autocompletion.png +0 -0
  174. data/docs/assets/playground_preview.png +0 -0
  175. data/docs/coi-serviceworker.js +0 -82
  176. data/docs/office_thread.js +0 -77
  177. data/docs/preview.html +0 -719
  178. data/docs/visual/VisualGallery.md +0 -4672
  179. data/docs/wasm/ruby.wasm +0 -0
  180. data/docs/wasm/wasm_doc_helper.css +0 -351
  181. data/docs/wasm/wasm_doc_helper.js +0 -439
  182. data/docs/zeta.js +0 -1107
  183. data/rbs_collection.lock.yaml +0 -252
  184. data/rbs_collection.yaml +0 -19
  185. data/vendor/sdk_runner/Program.cs +0 -93
  186. data/vendor/sdk_runner/sdk_runner.csproj +0 -13
@@ -1,439 +0,0 @@
1
- // wasm_doc_helper.js
2
- (function () {
3
- // Detect all pre.ruby blocks to setup interactive playgrounds
4
- document.addEventListener("DOMContentLoaded", () => {
5
- const codeBlocks = document.querySelectorAll("pre.ruby");
6
- codeBlocks.forEach((block, index) => {
7
- setupLazyPlayground(block, index);
8
- });
9
- });
10
-
11
- let rubyVM = null;
12
- let isInitializing = false;
13
- const initListeners = [];
14
- let consoleBuffer = ""; // Temporary buffer to capture stdout/stderr
15
-
16
- // Helper to initialize and retrieve the Ruby Wasm VM
17
- async function ensureRubyWasm() {
18
- if (rubyVM) return rubyVM;
19
- if (isInitializing) {
20
- return new Promise((resolve) => initListeners.push(resolve));
21
- }
22
- isInitializing = true;
23
-
24
- // Load browser.umd.js locally using the relative path prefix
25
- const prefix = window.rdoc_rel_prefix || "./";
26
- await loadScript(`${prefix}wasm/browser.umd.js`);
27
-
28
- // Fetch and compile the custom prepackaged ruby.wasm module
29
- const response = await fetch(`${prefix}wasm/ruby.wasm?cb=${Date.now()}`);
30
- const buffer = await response.arrayBuffer();
31
- const module = await WebAssembly.compile(buffer);
32
-
33
- // Capture stdout/stderr streams to buffer during execution
34
- const rubyWasm = window.rubyWasm || window["ruby-wasm-wasi"];
35
- const DefaultRubyVM = rubyWasm.DefaultRubyVM;
36
- let vmInit;
37
-
38
- const options = {
39
- stdout: (str) => { consoleBuffer += str; },
40
- stderr: (str) => { consoleBuffer += str; }
41
- };
42
-
43
- // Handle initialization polymorphism across various ruby-wasm versions
44
- if (typeof DefaultRubyVM.initialize === "function") {
45
- vmInit = DefaultRubyVM.initialize(module, options);
46
- } else if (typeof DefaultRubyVM.initializeVM === "function") {
47
- vmInit = DefaultRubyVM.initializeVM(module, options);
48
- } else if (typeof DefaultRubyVM === "function") {
49
- try {
50
- vmInit = DefaultRubyVM(module, options);
51
- } catch (e) {
52
- const instance = new DefaultRubyVM();
53
- const initFn = instance.initialize || instance.initializeVM;
54
- if (typeof initFn === "function") {
55
- vmInit = initFn.call(instance, module, options);
56
- } else {
57
- throw new Error("No initialization method found on DefaultRubyVM instance");
58
- }
59
- }
60
- } else {
61
- throw new Error("DefaultRubyVM is not available");
62
- }
63
- const { vm } = await vmInit;
64
-
65
- rubyVM = vm;
66
-
67
- isInitializing = false;
68
- const listeners = [...initListeners];
69
- initListeners.length = 0;
70
- listeners.forEach((resolve) => resolve(rubyVM));
71
- return rubyVM;
72
- }
73
-
74
- // Inject script tags dynamically
75
- function loadScript(src) {
76
- return new Promise((resolve, reject) => {
77
- if (document.querySelector(`script[src="${src}"]`)) {
78
- resolve();
79
- return;
80
- }
81
- const script = document.createElement("script");
82
- script.src = src;
83
- script.onload = resolve;
84
- script.onerror = reject;
85
- document.head.appendChild(script);
86
- });
87
- }
88
-
89
- // Setup lightweight wrapper and action buttons, deferring full editor instantiation until hover or click
90
- function setupLazyPlayground(block, index) {
91
- const originalCode = block.textContent;
92
-
93
- // Create wrapper container
94
- const wrapper = document.createElement("div");
95
- wrapper.className = "wasm-playground-wrapper";
96
- block.parentNode.insertBefore(wrapper, block);
97
- wrapper.appendChild(block);
98
-
99
- // Create action button bar (always visible)
100
- const btnBar = document.createElement("div");
101
- btnBar.className = "wasm-quick-action-bar";
102
-
103
- const previewBtn = document.createElement("button");
104
- previewBtn.className = "wasm-quick-btn wasm-quick-preview-btn";
105
- previewBtn.innerHTML = `
106
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px; vertical-align: middle;">
107
- <polygon points="5 3 19 12 5 21 5 3"></polygon>
108
- </svg>Live Preview
109
- `;
110
-
111
- const downloadBtn = document.createElement("button");
112
- downloadBtn.className = "wasm-quick-btn wasm-quick-download-btn";
113
- downloadBtn.innerHTML = `
114
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px; vertical-align: middle;">
115
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
116
- <polyline points="7 10 12 15 17 10"></polyline>
117
- <line x1="12" y1="15" x2="12" y2="3"></line>
118
- </svg>Download XLSX
119
- `;
120
-
121
- const resetBtn = document.createElement("button");
122
- resetBtn.className = "wasm-quick-btn wasm-quick-reset-btn";
123
- resetBtn.innerHTML = `
124
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px; vertical-align: middle;">
125
- <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
126
- <polyline points="3 3 3 8 8 8"></polyline>
127
- </svg>Reset
128
- `;
129
-
130
- btnBar.appendChild(previewBtn);
131
- btnBar.appendChild(downloadBtn);
132
- btnBar.appendChild(resetBtn);
133
-
134
- // Insert button bar right below the wrapper in the DOM
135
- wrapper.parentNode.insertBefore(btnBar, wrapper.nextSibling);
136
-
137
- let isEditorInitialized = false;
138
- let textarea = null;
139
-
140
- // Full initialization of the transparent overlay editor (Deferred)
141
- function initializeEditorOnDemand() {
142
- if (isEditorInitialized) return;
143
- isEditorInitialized = true;
144
-
145
- // Create transparent textarea overlay
146
- textarea = document.createElement("textarea");
147
- textarea.className = "wasm-inline-editor";
148
- textarea.value = originalCode;
149
- textarea.spellcheck = false;
150
- wrapper.insertBefore(textarea, block);
151
-
152
- // Copy exact layout styles from pre block to align overlay perfectly
153
- const preStyle = window.getComputedStyle(block);
154
- const stylesToCopy = [
155
- "fontFamily", "fontSize", "lineHeight", "fontWeight",
156
- "paddingTop", "paddingBottom", "paddingLeft", "paddingRight",
157
- "marginTop", "marginBottom", "marginLeft", "marginRight",
158
- "textAlign"
159
- ];
160
- stylesToCopy.forEach(prop => {
161
- textarea.style[prop] = preStyle[prop];
162
- });
163
-
164
- // Enforce overlapping styles
165
- block.style.position = "relative";
166
- block.style.pointerEvents = "none"; // Clicks pass to textarea
167
- block.style.whiteSpace = "pre-wrap";
168
- block.style.wordBreak = "break-all";
169
- block.style.zIndex = "1";
170
- block.style.margin = "0";
171
-
172
- textarea.style.position = "absolute";
173
- textarea.style.top = "0";
174
- textarea.style.left = "0";
175
- textarea.style.width = "100%";
176
- textarea.style.height = "100%";
177
- textarea.style.background = "transparent";
178
- textarea.style.color = "transparent";
179
- textarea.style.caretColor = "#2563eb"; // Sleek blue cursor
180
- textarea.style.border = "none";
181
- textarea.style.resize = "none";
182
- textarea.style.overflow = "hidden";
183
- textarea.style.whiteSpace = "pre-wrap";
184
- textarea.style.wordBreak = "break-all";
185
- textarea.style.zIndex = "2";
186
- textarea.style.outline = "none";
187
-
188
- textarea.addEventListener("input", updateHighlight);
189
- updateHighlight();
190
- }
191
-
192
- function updateHighlight() {
193
- if (!textarea) return;
194
- let code = textarea.value;
195
- if (code.endsWith("\n")) {
196
- code += " ";
197
- }
198
- block.innerHTML = highlightRuby(code);
199
-
200
- // Auto-grow height dynamically to fit text
201
- textarea.style.height = "auto";
202
- textarea.style.height = textarea.scrollHeight + "px";
203
- block.style.height = textarea.scrollHeight + "px";
204
- wrapper.style.height = textarea.scrollHeight + "px";
205
- }
206
-
207
- // Trigger full initialization on mouse hover or click
208
- wrapper.addEventListener("mouseenter", initializeEditorOnDemand, { once: true });
209
- wrapper.addEventListener("click", initializeEditorOnDemand);
210
-
211
- // RDoc-compatible Ruby Syntax Highlighter (Safe placeholder method)
212
- function highlightRuby(code) {
213
- let html = code
214
- .replace(/&/g, "&amp;")
215
- .replace(/</g, "&lt;")
216
- .replace(/>/g, "&gt;");
217
-
218
- const placeholders = [];
219
- function addPlaceholder(val, className) {
220
- const id = `__WASM_HL_${placeholders.length}__`;
221
- placeholders.push({ id, val, className });
222
- return id;
223
- }
224
-
225
- // 1. Strings (double quotes)
226
- html = html.replace(/"[^"\\]*(?:\\.[^"\\]*)*"/g, (match) => {
227
- return addPlaceholder(match, "ruby-string");
228
- });
229
- // 2. Strings (single quotes)
230
- html = html.replace(/'[^'\\]*(?:\\.[^'\\]*)*'/g, (match) => {
231
- return addPlaceholder(match, "ruby-string");
232
- });
233
-
234
- // 3. Comments (safe from backtracking, only comments starting after whitespace or at line start)
235
- html = html.replace(/(^|\s)(#[^\n]*)$/gm, (match, p1, p2) => {
236
- return p1 + addPlaceholder(p2, "ruby-comment");
237
- });
238
-
239
- // 4. Keywords
240
- const keywords = /\b(require|do|end|if|else|elsif|unless|while|until|for|in|class|module|def|return|true|false|nil)\b/g;
241
- html = html.replace(keywords, (match) => {
242
- return addPlaceholder(match, "ruby-keyword");
243
- });
244
-
245
- // 5. Constants (Capitalized names)
246
- const constants = /\b([A-Z][a-zA-Z0-9_]*)\b/g;
247
- html = html.replace(constants, (match) => {
248
- return addPlaceholder(match, "ruby-constant");
249
- });
250
-
251
- // 6. Simple numeric values
252
- const numericValues = /\b(\d+)\b/g;
253
- html = html.replace(numericValues, (match) => {
254
- return addPlaceholder(match, "ruby-value");
255
- });
256
-
257
- // 7. Specific xlsxrb APIs
258
- const apis = /\b(generate|add_style|add_sheet|add_row|set_column|set_print_option|border_all|border_bottom|border_top|border_left|border_right|align_horizontal|align_vertical|bold|italic|size|fill_color|number_format)\b/g;
259
- html = html.replace(apis, (match) => {
260
- return addPlaceholder(match, "ruby-identifier");
261
- });
262
-
263
- // Rehydrate placeholders in reverse order to avoid nested conflicts
264
- for (let i = placeholders.length - 1; i >= 0; i--) {
265
- const item = placeholders[i];
266
- html = html.replace(item.id, `<span class="${item.className}">${item.val}</span>`);
267
- }
268
-
269
- return html;
270
- }
271
-
272
-
273
-
274
- // Click: Live Preview (1-click auto run)
275
- previewBtn.addEventListener("click", () => {
276
- const codeToRun = textarea ? textarea.value : originalCode;
277
- triggerLivePreview(codeToRun);
278
- });
279
-
280
- // Click: Download XLSX directly
281
- downloadBtn.addEventListener("click", async () => {
282
- downloadBtn.disabled = true;
283
- const originalText = downloadBtn.innerHTML;
284
- downloadBtn.innerHTML = `Running Wasm...`;
285
-
286
- try {
287
- const vm = await ensureRubyWasm();
288
- clearXlsxFiles(vm, "/");
289
- let userCode = textarea ? textarea.value : originalCode;
290
- if (!/require\s+['"]xlsxrb['"]/.test(userCode)) {
291
- userCode = 'require "xlsxrb"\n' + userCode;
292
- }
293
- userCode = '$LOAD_PATH << Dir.pwd unless $LOAD_PATH.include?(Dir.pwd)\n' + userCode;
294
-
295
- vm.eval(userCode);
296
- const generatedFiles = scanXlsxFiles(vm, "/");
297
- if (generatedFiles.length > 0) {
298
- downloadFileFromVfs(vm, generatedFiles[0]);
299
- } else {
300
- alert("Error: No XLSX file was generated.");
301
- }
302
- } catch (err) {
303
- alert("Error generating XLSX: " + err.message);
304
- console.error(err);
305
- } finally {
306
- downloadBtn.disabled = false;
307
- downloadBtn.innerHTML = originalText;
308
- }
309
- });
310
-
311
- // Click: Reset Code
312
- resetBtn.addEventListener("click", () => {
313
- if (textarea) {
314
- textarea.value = originalCode;
315
- updateHighlight();
316
- }
317
- });
318
-
319
-
320
-
321
- }
322
-
323
- // Open the sliding drawer panel and automatically execute code preview inside the iframe
324
- function triggerLivePreview(code) {
325
- const drawer = ensurePreviewDrawer();
326
- const iframe = document.getElementById("wasmPreviewIframe");
327
- const prefix = window.rdoc_rel_prefix || "./";
328
-
329
- // Show the drawer
330
- drawer.classList.add("open");
331
-
332
- // Initialize or load the iframe
333
- const targetSrc = `${prefix}preview.html`;
334
- if (!iframe.src || !iframe.src.includes("preview.html")) {
335
- iframe.src = targetSrc;
336
- iframe.onload = () => {
337
- sendCodeToIframe();
338
- };
339
- } else {
340
- sendCodeToIframe();
341
- }
342
-
343
- function sendCodeToIframe() {
344
- if (iframe.contentWindow) {
345
- iframe.contentWindow.postMessage({
346
- action: "load_code",
347
- code: code
348
- }, "*");
349
- }
350
- }
351
- }
352
-
353
- // Delete all .xlsx files recursively under the virtual directory
354
- function clearXlsxFiles(vm, dir) {
355
- try {
356
- vm.eval(`
357
- Dir.glob("${dir}/**/*.xlsx").each do |file|
358
- File.delete(file) rescue nil
359
- end
360
- `);
361
- } catch (e) {
362
- console.error("Error clearing XLSX files:", e);
363
- }
364
- }
365
-
366
- // Recursively scan virtual directory for generated .xlsx files
367
- function scanXlsxFiles(vm, dir) {
368
- try {
369
- const filesStr = vm.eval(`
370
- Dir.glob("${dir}/**/*.xlsx").join(",")
371
- `).toString();
372
- return filesStr ? filesStr.split(",") : [];
373
- } catch (e) {
374
- console.error("Error scanning XLSX files:", e);
375
- return [];
376
- }
377
- }
378
-
379
- // Fetch binary contents of a VFS file and trigger browser download
380
- function downloadFileFromVfs(vm, filepath) {
381
- try {
382
- // Hex bridge unpacking to transfer binary data safely to JS
383
- const hexData = vm.eval(`
384
- File.binread("${filepath}").unpack1("H*")
385
- `).toString();
386
-
387
- const len = hexData.length;
388
- const bytes = new Uint8Array(len / 2);
389
- for (let i = 0; i < len; i += 2) {
390
- bytes[i / 2] = parseInt(hexData.substr(i, 2), 16);
391
- }
392
-
393
- const filename = filepath.split("/").pop();
394
- const blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
395
- const url = URL.createObjectURL(blob);
396
- const a = document.createElement("a");
397
- a.href = url;
398
- a.download = filename;
399
- document.body.appendChild(a);
400
- a.click();
401
- document.body.removeChild(a);
402
- setTimeout(() => URL.revokeObjectURL(url), 10000);
403
-
404
- } catch (e) {
405
- console.error("Error downloading file from Vfs:", e);
406
- }
407
- }
408
-
409
- // Create or retrieve the side drawer preview panel
410
- function ensurePreviewDrawer() {
411
- let drawer = document.getElementById("wasmPreviewDrawer");
412
- if (drawer) return drawer;
413
-
414
- drawer = document.createElement("div");
415
- drawer.id = "wasmPreviewDrawer";
416
- drawer.className = "wasm-preview-drawer";
417
- drawer.innerHTML = `
418
- <div class="drawer-header">
419
- <span class="drawer-title">Live Spreadsheet Preview</span>
420
- <button class="drawer-close-btn">&times;</button>
421
- </div>
422
- <div class="drawer-body">
423
- <iframe id="wasmPreviewIframe" class="drawer-iframe" src=""></iframe>
424
- </div>
425
- `;
426
- document.body.appendChild(drawer);
427
-
428
- // Setup close action
429
- const closeBtn = drawer.querySelector(".drawer-close-btn");
430
- closeBtn.addEventListener("click", () => {
431
- drawer.classList.remove("open");
432
- // Clear iframe src on close to stop background Wasm threads and free memory
433
- const iframe = document.getElementById("wasmPreviewIframe");
434
- if (iframe) iframe.src = "";
435
- });
436
-
437
- return drawer;
438
- }
439
- })();