scriptorium 0.0.3 → 0.7.2

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 (353) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +324 -0
  3. data/README.md +3155 -1
  4. data/assets/.DS_Store +0 -0
  5. data/assets/README.md +44 -0
  6. data/assets/icons/social/reddit.png +0 -0
  7. data/assets/icons/social/x-logo.png +0 -0
  8. data/assets/icons/ui/.DS_Store +0 -0
  9. data/assets/icons/ui/back.png +0 -0
  10. data/assets/icons/ui/copy.png +0 -0
  11. data/assets/icons/ui/down.png +0 -0
  12. data/assets/icons/ui/end.png +0 -0
  13. data/assets/icons/ui/exit.png +0 -0
  14. data/assets/icons/ui/foo +10 -0
  15. data/assets/icons/ui/home.png +0 -0
  16. data/assets/icons/ui/left.png +0 -0
  17. data/assets/icons/ui/next.png +0 -0
  18. data/assets/icons/ui/right.png +0 -0
  19. data/assets/icons/ui/start.png +0 -0
  20. data/assets/icons/ui/up.png +0 -0
  21. data/assets/imagenotfound.jpg +0 -0
  22. data/assets/samples/placeholder.svg +9 -0
  23. data/assets/themes/standard/favicon.svg +6 -0
  24. data/bin/sblog +84 -5
  25. data/bin/scriptorium +1 -0
  26. data/doc/README.txt +6 -0
  27. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +94 -0
  28. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +2 -0
  29. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +45 -0
  30. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
  31. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
  32. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
  33. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +35 -0
  34. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +27 -0
  35. data/doc/anti-amnesia/20250807-213025.md +116 -0
  36. data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
  37. data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
  38. data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
  39. data/doc/banner_svg_config.md +114 -0
  40. data/doc/contrib.lt3 +8 -0
  41. data/doc/dependencies.md +281 -0
  42. data/doc/hacker.lt3 +5 -0
  43. data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
  44. data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
  45. data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
  46. data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
  47. data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
  48. data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
  49. data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
  50. data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
  51. data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
  52. data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
  53. data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
  54. data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
  55. data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
  56. data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
  57. data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
  58. data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
  59. data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
  60. data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
  61. data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
  62. data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
  63. data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
  64. data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
  65. data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
  66. data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
  67. data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
  68. data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
  69. data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
  70. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
  71. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
  72. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
  73. data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
  74. data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
  75. data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
  76. data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
  77. data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
  78. data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
  79. data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
  80. data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
  81. data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
  82. data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
  83. data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
  84. data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
  85. data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
  86. data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
  87. data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
  88. data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
  89. data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
  90. data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
  91. data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
  92. data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
  93. data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
  94. data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
  95. data/doc/imported/0018-ruby-puns/post.html +46 -0
  96. data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
  97. data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
  98. data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
  99. data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
  100. data/doc/imported/0020-fffff/metadata.txt +7 -0
  101. data/doc/imported/0020-fffff/post.html +24 -0
  102. data/doc/imported/0020-fffff/source.lt3 +12 -0
  103. data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
  104. data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
  105. data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
  106. data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
  107. data/doc/imported/0023-doctor-sleep/post.html +63 -0
  108. data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
  109. data/doc/imported/0024-just-a-test/metadata.txt +7 -0
  110. data/doc/imported/0024-just-a-test/post.html +24 -0
  111. data/doc/imported/0024-just-a-test/source.lt3 +12 -0
  112. data/doc/imported/import_summary.txt +98 -0
  113. data/doc/livetext-informal-spec.txt +65 -0
  114. data/doc/myuserdoc/ch-0.lt3 +31 -0
  115. data/doc/myuserdoc/ch-1.lt3 +37 -0
  116. data/doc/myuserdoc/ch-10.lt3 +22 -0
  117. data/doc/myuserdoc/ch-2.lt3 +37 -0
  118. data/doc/myuserdoc/ch-3.lt3 +19 -0
  119. data/doc/myuserdoc/ch-4.lt3 +43 -0
  120. data/doc/myuserdoc/ch-5.lt3 +22 -0
  121. data/doc/myuserdoc/ch-6.lt3 +19 -0
  122. data/doc/myuserdoc/ch-7.lt3 +16 -0
  123. data/doc/myuserdoc/ch-8.lt3 +13 -0
  124. data/doc/myuserdoc/ch-9.lt3 +19 -0
  125. data/doc/myuserdoc/tweak.rb +18 -0
  126. data/doc/myuserdoc/userdoc-toc.txt +88 -0
  127. data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
  128. data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
  129. data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
  130. data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
  131. data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
  132. data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
  133. data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
  134. data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
  135. data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
  136. data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
  137. data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
  138. data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
  139. data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
  140. data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
  141. data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
  142. data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
  143. data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
  144. data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
  145. data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
  146. data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
  147. data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
  148. data/doc/old-posts/0024-just-a-test.lt3 +12 -0
  149. data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
  150. data/doc/old-repo +1 -0
  151. data/doc/reddit_credentials_template.json +8 -0
  152. data/doc/reddit_integration.md +207 -0
  153. data/doc/user.lt3 +35 -0
  154. data/doc/user_guide_section_1.md +137 -0
  155. data/doc/user_guide_section_10.md +515 -0
  156. data/doc/user_guide_section_11.md +708 -0
  157. data/doc/user_guide_section_2.md +233 -0
  158. data/doc/user_guide_section_3.md +5 -0
  159. data/doc/user_guide_section_4.md +221 -0
  160. data/doc/user_guide_section_5.md +243 -0
  161. data/doc/user_guide_section_6.md +147 -0
  162. data/doc/user_guide_section_7.md +311 -0
  163. data/doc/user_guide_section_8.md +224 -0
  164. data/doc/user_guide_section_9.md +375 -0
  165. data/lib/rouge/lexers/livetext.rb +74 -0
  166. data/lib/scriptorium/api.rb +2373 -0
  167. data/lib/scriptorium/banner_svg.rb +729 -0
  168. data/lib/scriptorium/contract.rb +34 -0
  169. data/lib/scriptorium/exceptions.rb +201 -1
  170. data/lib/scriptorium/helpers.rb +675 -0
  171. data/lib/scriptorium/post.rb +259 -0
  172. data/lib/scriptorium/reddit.rb +83 -0
  173. data/lib/scriptorium/repo.rb +938 -0
  174. data/lib/scriptorium/standard_files.rb +149 -0
  175. data/lib/scriptorium/support/bootstrap/css.txt +5 -0
  176. data/lib/scriptorium/support/bootstrap/js.txt +4 -0
  177. data/lib/scriptorium/support/common_js/clipboard.js +35 -0
  178. data/lib/scriptorium/support/common_js/content-loader.js +187 -0
  179. data/lib/scriptorium/support/common_js/navigation.js +52 -0
  180. data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
  181. data/lib/scriptorium/support/config/reddit.txt +10 -0
  182. data/lib/scriptorium/support/config/reddit_template.txt +17 -0
  183. data/lib/scriptorium/support/config/social.txt +8 -0
  184. data/lib/scriptorium/support/highlight/css.txt +2 -0
  185. data/lib/scriptorium/support/highlight/custom.css +119 -0
  186. data/lib/scriptorium/support/highlight/js.txt +1 -0
  187. data/lib/scriptorium/support/post_index/config.txt +15 -0
  188. data/lib/scriptorium/support/post_index/style.css +55 -0
  189. data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
  190. data/lib/scriptorium/support/templates/initial_post.lt3 +12 -0
  191. data/lib/scriptorium/support/templates/layout.txt +5 -0
  192. data/lib/scriptorium/support/templates/post.lt3 +104 -0
  193. data/lib/scriptorium/support/theme/footer.lt3 +2 -0
  194. data/lib/scriptorium/support/theme/header.lt3 +4 -0
  195. data/lib/scriptorium/support/theme/left.lt3 +3 -0
  196. data/lib/scriptorium/support/theme/main.lt3 +5 -0
  197. data/lib/scriptorium/support/theme/right.lt3 +3 -0
  198. data/lib/scriptorium/theme.rb +192 -0
  199. data/lib/scriptorium/version.rb +1 -1
  200. data/lib/scriptorium/view.rb +1021 -0
  201. data/lib/scriptorium/widgets/featured_posts.rb +149 -0
  202. data/lib/scriptorium/widgets/links.rb +112 -0
  203. data/lib/scriptorium/widgets/pages.rb +133 -0
  204. data/lib/scriptorium/widgets/widget.rb +133 -0
  205. data/lib/scriptorium.rb +38 -34
  206. data/lib/skeleton.rb +10 -1
  207. data/scriptorium.gemspec +17 -5
  208. data/test/README.md +69 -0
  209. data/test/WEB_INTEGRATION_README.md +196 -0
  210. data/test/all +83 -0
  211. data/test/api_demo.rb +99 -0
  212. data/test/assets/imagenotfound.jpg +0 -0
  213. data/test/assets/images/.DS_Store +0 -0
  214. data/test/assets/images/README.md +27 -0
  215. data/test/assets/images/odd_aspect.png +0 -0
  216. data/test/assets/images/perfect.png +0 -0
  217. data/test/assets/images/small.png +0 -0
  218. data/test/assets/images/tall.png +0 -0
  219. data/test/assets/images/very_tall.png +0 -0
  220. data/test/assets/images/very_wide.png +0 -0
  221. data/test/assets/images/wide.png +0 -0
  222. data/test/assets/testbanner.jpg +0 -0
  223. data/test/banner_svg/simple_helpers.rb +13 -0
  224. data/test/banner_svg/unit.rb +1000 -0
  225. data/test/config/deployment.txt +5 -0
  226. data/test/ed_test.rb +204 -0
  227. data/test/integration/cursor_banner_combinations.rb +193 -0
  228. data/test/integration/cursor_banner_features.rb +374 -0
  229. data/test/integration/integration_test.rb +326 -0
  230. data/test/integration/preview_flow_test.rb +94 -0
  231. data/test/livetext_plugin_test.rb +500 -0
  232. data/test/manual/asset_mgmt.rb +67 -0
  233. data/test/manual/banner-tests/index.html +45 -0
  234. data/test/manual/banner-tests/svg.txt +3 -0
  235. data/test/manual/banner-tests/test01.html +122 -0
  236. data/test/manual/banner-tests/test02.html +122 -0
  237. data/test/manual/banner-tests/test03.html +122 -0
  238. data/test/manual/banner-tests/test04.html +129 -0
  239. data/test/manual/banner-tests/test05.html +129 -0
  240. data/test/manual/banner-tests/test06.html +129 -0
  241. data/test/manual/banner-tests/test07.html +129 -0
  242. data/test/manual/banner-tests/test08.html +123 -0
  243. data/test/manual/banner-tests/test09.html +123 -0
  244. data/test/manual/banner-tests/test10.html +123 -0
  245. data/test/manual/banner-tests/test11.html +123 -0
  246. data/test/manual/banner-tests/test12.html +123 -0
  247. data/test/manual/banner-tests/test13.html +123 -0
  248. data/test/manual/banner-tests/test14.html +123 -0
  249. data/test/manual/banner-tests/test15.html +122 -0
  250. data/test/manual/banner-tests/test16.html +122 -0
  251. data/test/manual/banner-tests/test17.html +122 -0
  252. data/test/manual/banner-tests/test18.html +132 -0
  253. data/test/manual/banner-tests/test19.html +132 -0
  254. data/test/manual/banner-tests/test20.html +132 -0
  255. data/test/manual/banner-tests/test21.html +132 -0
  256. data/test/manual/banner-tests/test22.html +132 -0
  257. data/test/manual/banner-tests/test23.html +132 -0
  258. data/test/manual/banner-tests/test24.html +132 -0
  259. data/test/manual/banner-tests/test25.html +131 -0
  260. data/test/manual/banner_environment.rb +205 -0
  261. data/test/manual/codemirror_demo.html +773 -0
  262. data/test/manual/create_posts_for_web.rb +114 -0
  263. data/test/manual/environment.rb +67 -0
  264. data/test/manual/make_banner.rb +153 -0
  265. data/test/manual/preview_manual_test.rb +129 -0
  266. data/test/manual/sample_banner_config.txt +12 -0
  267. data/test/manual/test_advanced_widgets.rb +73 -0
  268. data/test/manual/test_banner_combinations.rb +120 -0
  269. data/test/manual/test_banner_features.rb +306 -0
  270. data/test/manual/test_banner_integration.rb +115 -0
  271. data/test/manual/test_banner_radial.rb +87 -0
  272. data/test/manual/test_basic_posts.rb +47 -0
  273. data/test/manual/test_layout_widgets.rb +40 -0
  274. data/test/manual/test_pagination.rb +24 -0
  275. data/test/manual/test_random_posts.rb +38 -0
  276. data/test/manual/test_syntax_highlighting.rb +167 -0
  277. data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
  278. data/test/rubytext/rubytext_demo_test.rb +42 -0
  279. data/test/rubytext/rubytext_testing_guide.md +277 -0
  280. data/test/run_automated_tests.rb +45 -0
  281. data/test/staging/.DS_Store +0 -0
  282. data/test/support/preview_utils.rb +88 -0
  283. data/test/syntax_highlighting_test.lt3 +124 -0
  284. data/test/test_gem_assets.rb +48 -0
  285. data/test/test_helpers.rb +240 -0
  286. data/test/tui_editor_integration_test.rb +296 -0
  287. data/test/tui_integration_test.rb +883 -0
  288. data/test/unit/api.rb +1776 -0
  289. data/test/unit/asset_management.rb +219 -0
  290. data/test/unit/backup_test.rb +451 -0
  291. data/test/unit/clipboard_test.rb +60 -0
  292. data/test/unit/contract_test.rb +69 -0
  293. data/test/unit/core.rb +1211 -0
  294. data/test/unit/deploy_config_test.rb +248 -0
  295. data/test/unit/deploy_test.rb +478 -0
  296. data/test/unit/edit_post_test.rb +168 -0
  297. data/test/unit/gem_asset_management.rb +183 -0
  298. data/test/unit/livetext_basic.rb +57 -0
  299. data/test/unit/livetext_compatibility.rb +82 -0
  300. data/test/unit/parse_cmd_test.rb +260 -0
  301. data/test/unit/permalink_copy_test.rb +211 -0
  302. data/test/unit/post.rb +309 -0
  303. data/test/unit/post_index_config_test.rb +258 -0
  304. data/test/unit/post_state_helpers_test.rb +137 -0
  305. data/test/unit/read_commented_file_test.rb +278 -0
  306. data/test/unit/reddit_test.rb +235 -0
  307. data/test/unit/repo.rb +569 -0
  308. data/test/unit/social_test.rb +366 -0
  309. data/test/unit/syntax_highlighting.rb +70 -0
  310. data/test/unit/theme_management_test.rb +91 -0
  311. data/test/unit/view.rb +498 -0
  312. data/test/unit/widgets.rb +669 -0
  313. data/test/web_integration_test.rb +231 -0
  314. data/test/web_test_helper.rb +218 -0
  315. data/test/web_workflow_test.rb +527 -0
  316. data/test/wizard_test.rb +123 -0
  317. data/ui/README.md +67 -0
  318. data/ui/common/lib/ui_common.rb +8 -0
  319. data/ui/rubytext/README.md +191 -0
  320. data/ui/rubytext/bin/scriptorium-rubytext +402 -0
  321. data/ui/rubytext/lib/rubytext_ui.rb +300 -0
  322. data/ui/tui/bin/scriptorium +1890 -0
  323. data/ui/tui/test/tui_test.rb +23 -0
  324. data/ui/web/app/app.rb +2600 -0
  325. data/ui/web/app/assets/livetext_mode.js +244 -0
  326. data/ui/web/app/error_helpers.rb +150 -0
  327. data/ui/web/app/views/advanced_config.erb +196 -0
  328. data/ui/web/app/views/asset_management.erb +645 -0
  329. data/ui/web/app/views/backup_management.erb +238 -0
  330. data/ui/web/app/views/banner_config.erb +200 -0
  331. data/ui/web/app/views/config_widget.erb +232 -0
  332. data/ui/web/app/views/configure_view.erb +401 -0
  333. data/ui/web/app/views/dashboard.erb +154 -0
  334. data/ui/web/app/views/deploy_config.erb +149 -0
  335. data/ui/web/app/views/edit_pages.erb +363 -0
  336. data/ui/web/app/views/edit_post.erb +175 -0
  337. data/ui/web/app/views/edit_theme.erb +73 -0
  338. data/ui/web/app/views/edit_theme_file.erb +74 -0
  339. data/ui/web/app/views/error_page.erb +29 -0
  340. data/ui/web/app/views/header_config.erb +155 -0
  341. data/ui/web/app/views/layout_config.erb +147 -0
  342. data/ui/web/app/views/navbar_config.erb +411 -0
  343. data/ui/web/app/views/theme_management.erb +130 -0
  344. data/ui/web/app/views/view_dashboard.erb +779 -0
  345. data/ui/web/app/views/widgets.erb +249 -0
  346. data/ui/web/bin/scriptorium-web +164 -0
  347. data/ui/web/test/web_basic_test.rb +38 -0
  348. data/ui/web/test_navbar.txt +7 -0
  349. data/ui/web/tmp/timing.log +17 -0
  350. data/ui/web/tmp/web_server.log +0 -0
  351. metadata +434 -8
  352. data/lib/scriptorium/engine.rb +0 -22
  353. data/test/engine/unit.rb +0 -44
@@ -0,0 +1,401 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Configure View - Scriptorium Web UI</title>
5
+ <style>
6
+ body { font-family: Arial, sans-serif; margin: 20px; }
7
+ .header { border-bottom: 1px solid #ccc; padding-bottom: 10px; margin-bottom: 20px; }
8
+ .status { padding: 8px; margin: 8px 0; border-radius: 3px; }
9
+ .error { background-color: #ffebee; color: #c62828; border: 1px solid #ffcdd2; }
10
+ .success { background-color: #e8f5e8; color: #2e7d32; border: 1px solid #c8e6c9; }
11
+ .button { background: #007cba; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px; }
12
+ .button:hover { background: #005a87; }
13
+ .button.secondary { background: #6c757d; }
14
+ .button.secondary:hover { background: #545b62; }
15
+ .button:disabled { background: #ccc; cursor: not-allowed; }
16
+ .section { margin: 20px 0; }
17
+ .section h2 { margin: 0 0 15px 0; font-size: 18px; }
18
+ textarea { width: 100%; height: 200px; font-family: 'Courier New', monospace; font-size: 14px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
19
+ input[type="text"], select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 10px; }
20
+ .view-info { background: #f8f9fa; padding: 15px; border-radius: 4px; margin-bottom: 20px; }
21
+ .view-info h2 { margin: 0 0 10px 0; }
22
+ .view-info p { margin: 5px 0; }
23
+
24
+ /* Step Navigation */
25
+ .step-nav { display: flex; margin-bottom: 30px; border-bottom: 2px solid #eee; }
26
+ .step { flex: 1; text-align: center; padding: 15px; cursor: pointer; border-bottom: 3px solid transparent; }
27
+ .step.active { border-bottom-color: #007cba; background: #f0f8ff; }
28
+ .step.completed { border-bottom-color: #28a745; }
29
+ .step-number { display: inline-block; width: 30px; height: 30px; line-height: 30px; border-radius: 50%; background: #ccc; color: white; margin-right: 10px; }
30
+ .step.active .step-number { background: #007cba; }
31
+ .step.completed .step-number { background: #28a745; }
32
+
33
+ /* Step Content */
34
+ .step-content { display: none; }
35
+ .step-content.active { display: block; }
36
+
37
+ /* Layout Builder */
38
+ .layout-builder { border: 1px solid #ddd; padding: 20px; border-radius: 4px; }
39
+ .container-option { margin: 10px 0; padding: 10px; border: 1px solid #eee; border-radius: 4px; }
40
+ .container-option label { display: flex; align-items: center; cursor: pointer; }
41
+ .container-option input[type="checkbox"] { margin-right: 10px; }
42
+ .container-option input[type="text"] { width: 80px; margin-left: 10px; }
43
+ .layout-preview { margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; }
44
+ .layout-preview-grid { display: grid; gap: 5px; }
45
+ .layout-preview-grid.header { grid-template-columns: 1fr; }
46
+ .layout-preview-grid.body { grid-template-columns: 1fr 2fr 1fr; }
47
+ .layout-preview-grid.footer { grid-template-columns: 1fr; }
48
+ .layout-preview-cell { padding: 10px; background: #e9ecef; border-radius: 3px; text-align: center; font-size: 12px; }
49
+
50
+ /* Container Tabs */
51
+ .container-tabs { border-bottom: 1px solid #ddd; margin-bottom: 20px; }
52
+ .container-tab { display: inline-block; padding: 10px 20px; cursor: pointer; border: 1px solid transparent; border-bottom: none; margin-bottom: -1px; }
53
+ .container-tab.active { background: white; border-color: #ddd; border-radius: 4px 4px 0 0; }
54
+ .container-content { display: none; }
55
+ .container-content.active { display: block; }
56
+
57
+ /* Form Navigation */
58
+ .form-nav { margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
59
+ .form-nav .button { margin-right: 10px; }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <div class="header">
64
+ <h1>Configure View: <%= @view.name %></h1>
65
+ <p>Step-by-step view configuration wizard</p>
66
+ </div>
67
+
68
+ <% if params[:error] %>
69
+ <div class="status error">
70
+ <strong>Error:</strong> <%= params[:error] %>
71
+ <% if params[:suggestion] %>
72
+ <br><em>Suggestion:</em> <%= params[:suggestion] %>
73
+ <% end %>
74
+ </div>
75
+ <% end %>
76
+
77
+ <div class="view-info">
78
+ <h2><%= @view.title %></h2>
79
+ <p><strong>Name:</strong> <%= @view.name %></p>
80
+ <p><strong>Theme:</strong> <%= @view.theme %></p>
81
+ <p><strong>Path:</strong> views/<%= @view.name %></p>
82
+ </div>
83
+
84
+ <!-- Step Navigation -->
85
+ <div class="step-nav">
86
+ <div class="step active" onclick="showStep(1)">
87
+ <span class="step-number">1</span>
88
+ <span>Basic Info</span>
89
+ </div>
90
+ <div class="step" onclick="showStep(2)">
91
+ <span class="step-number">2</span>
92
+ <span>Layout</span>
93
+ </div>
94
+ <div class="step" onclick="showStep(3)">
95
+ <span class="step-number">3</span>
96
+ <span>Containers</span>
97
+ </div>
98
+ </div>
99
+
100
+ <form method="post" action="/save_view_config/<%= @view.name %>" id="configForm">
101
+ <!-- Step 1: Basic Information -->
102
+ <div id="step1" class="step-content active">
103
+ <div class="section">
104
+ <h2>Basic View Information</h2>
105
+ <p>Configure the basic properties of your view.</p>
106
+
107
+ <label for="view_title">View Title:</label>
108
+ <input type="text" id="view_title" name="view_title" value="<%= @view.title %>" required>
109
+
110
+ <label for="view_subtitle">Subtitle:</label>
111
+ <input type="text" id="view_subtitle" name="view_subtitle" value="<%= @view.subtitle %>">
112
+
113
+ <label for="view_theme">Theme:</label>
114
+ <select id="view_theme" name="view_theme">
115
+ <option value="standard" <%= 'selected' if @view.theme == 'standard' %>>Standard</option>
116
+ <!-- Add more themes as they become available -->
117
+ </select>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Step 2: Layout Definition -->
122
+ <div id="step2" class="step-content">
123
+ <div class="section">
124
+ <h2>Layout Definition</h2>
125
+ <p>Choose which containers to include in your layout and set their properties.</p>
126
+
127
+ <div class="layout-builder">
128
+ <div class="container-option">
129
+ <label>
130
+ <input type="checkbox" id="container_header" name="containers[]" value="header" checked>
131
+ Header (top section for banner, title, navigation)
132
+ </label>
133
+ </div>
134
+
135
+ <div class="container-option">
136
+ <label>
137
+ <input type="checkbox" id="container_left" name="containers[]" value="left">
138
+ Left Sidebar
139
+ <input type="text" id="left_width" name="left_width" value="15%" placeholder="15%">
140
+ </label>
141
+ </div>
142
+
143
+ <div class="container-option">
144
+ <label>
145
+ <input type="checkbox" id="container_main" name="containers[]" value="main" checked>
146
+ Main Content (posts, content)
147
+ </label>
148
+ </div>
149
+
150
+ <div class="container-option">
151
+ <label>
152
+ <input type="checkbox" id="container_right" name="containers[]" value="right">
153
+ Right Sidebar
154
+ <input type="text" id="right_width" name="right_width" value="15%" placeholder="15%">
155
+ </label>
156
+ </div>
157
+
158
+ <div class="container-option">
159
+ <label>
160
+ <input type="checkbox" id="container_footer" name="containers[]" value="footer" checked>
161
+ Footer (bottom section for copyright, links)
162
+ </label>
163
+ </div>
164
+ </div>
165
+
166
+ <div class="layout-preview">
167
+ <h3>Layout Preview</h3>
168
+ <div id="layoutPreview">
169
+ <!-- Dynamic preview will be generated here -->
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+ <!-- Step 3: Container Configuration -->
176
+ <div id="step3" class="step-content">
177
+ <div class="section">
178
+ <h2>Container Configuration</h2>
179
+ <p>Configure the content for each container in your layout.</p>
180
+
181
+ <div class="container-tabs" id="containerTabs">
182
+ <!-- Dynamic tabs will be generated here -->
183
+ </div>
184
+
185
+ <div id="containerContents">
186
+ <!-- Dynamic container content will be generated here -->
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
+ <!-- Form Navigation -->
192
+ <div class="form-nav">
193
+ <button type="button" class="button secondary" onclick="previousStep()" id="prevBtn" style="display: none;">Previous</button>
194
+ <button type="button" class="button" onclick="nextStep()" id="nextBtn">Next</button>
195
+ <button type="submit" class="button" id="saveBtn" style="display: none;">Save Configuration</button>
196
+ <a href="/" class="button secondary" style="text-decoration: none;">Cancel</a>
197
+ </div>
198
+ </form>
199
+
200
+ <script>
201
+ let currentStep = 1;
202
+ const totalSteps = 3;
203
+
204
+ function showStep(step) {
205
+ // Hide all step contents
206
+ for (let i = 1; i <= totalSteps; i++) {
207
+ document.getElementById(`step${i}`).classList.remove('active');
208
+ document.querySelector(`.step:nth-child(${i})`).classList.remove('active');
209
+ }
210
+
211
+ // Show selected step
212
+ document.getElementById(`step${step}`).classList.add('active');
213
+ document.querySelector(`.step:nth-child(${step})`).classList.add('active');
214
+
215
+ currentStep = step;
216
+ updateNavigation();
217
+
218
+ if (step === 2) {
219
+ updateLayoutPreview();
220
+ } else if (step === 3) {
221
+ generateContainerTabs();
222
+ }
223
+ }
224
+
225
+ function nextStep() {
226
+ if (currentStep < totalSteps) {
227
+ showStep(currentStep + 1);
228
+ }
229
+ }
230
+
231
+ function previousStep() {
232
+ if (currentStep > 1) {
233
+ showStep(currentStep - 1);
234
+ }
235
+ }
236
+
237
+ function updateNavigation() {
238
+ const prevBtn = document.getElementById('prevBtn');
239
+ const nextBtn = document.getElementById('nextBtn');
240
+ const saveBtn = document.getElementById('saveBtn');
241
+
242
+ prevBtn.style.display = currentStep > 1 ? 'inline-block' : 'none';
243
+ nextBtn.style.display = currentStep < totalSteps ? 'inline-block' : 'none';
244
+ saveBtn.style.display = currentStep === totalSteps ? 'inline-block' : 'none';
245
+ }
246
+
247
+ function updateLayoutPreview() {
248
+ const containers = [];
249
+ const checkboxes = document.querySelectorAll('input[name="containers[]"]:checked');
250
+
251
+ checkboxes.forEach(checkbox => {
252
+ const container = checkbox.value;
253
+ let width = '';
254
+
255
+ if (container === 'left') {
256
+ width = document.getElementById('left_width').value;
257
+ } else if (container === 'right') {
258
+ width = document.getElementById('right_width').value;
259
+ }
260
+
261
+ containers.push({ name: container, width: width });
262
+ });
263
+
264
+ const preview = document.getElementById('layoutPreview');
265
+ let html = '';
266
+
267
+ // Header
268
+ const header = containers.find(c => c.name === 'header');
269
+ if (header) {
270
+ html += '<div class="layout-preview-grid header"><div class="layout-preview-cell">Header</div></div>';
271
+ }
272
+
273
+ // Body (left, main, right)
274
+ const bodyContainers = containers.filter(c => ['left', 'main', 'right'].includes(c.name));
275
+ if (bodyContainers.length > 0) {
276
+ const columns = bodyContainers.map(c => {
277
+ if (c.name === 'left' || c.name === 'right') {
278
+ return c.width || '1fr';
279
+ }
280
+ return '2fr'; // main gets more space
281
+ }).join(' ');
282
+
283
+ html += `<div class="layout-preview-grid body" style="grid-template-columns: ${columns}">`;
284
+ bodyContainers.forEach(c => {
285
+ html += `<div class="layout-preview-cell">${c.name.charAt(0).toUpperCase() + c.name.slice(1)}</div>`;
286
+ });
287
+ html += '</div>';
288
+ }
289
+
290
+ // Footer
291
+ const footer = containers.find(c => c.name === 'footer');
292
+ if (footer) {
293
+ html += '<div class="layout-preview-grid footer"><div class="layout-preview-cell">Footer</div></div>';
294
+ }
295
+
296
+ preview.innerHTML = html;
297
+ }
298
+
299
+ function generateContainerTabs() {
300
+ const containers = [];
301
+ const checkboxes = document.querySelectorAll('input[name="containers[]"]:checked');
302
+
303
+ checkboxes.forEach(checkbox => {
304
+ containers.push(checkbox.value);
305
+ });
306
+
307
+ const tabsContainer = document.getElementById('containerTabs');
308
+ const contentsContainer = document.getElementById('containerContents');
309
+
310
+ // Generate tabs
311
+ let tabsHtml = '';
312
+ let contentsHtml = '';
313
+
314
+ containers.forEach((container, index) => {
315
+ const isActive = index === 0 ? 'active' : '';
316
+ const containerName = container.charAt(0).toUpperCase() + container.slice(1);
317
+
318
+ tabsHtml += `<div class="container-tab ${isActive}" onclick="showContainerTab('${container}')">${containerName}</div>`;
319
+ contentsHtml += `<div id="container_${container}" class="container-content ${isActive}">`;
320
+ contentsHtml += generateContainerContent(container);
321
+ contentsHtml += '</div>';
322
+ });
323
+
324
+ tabsContainer.innerHTML = tabsHtml;
325
+ contentsContainer.innerHTML = contentsHtml;
326
+ }
327
+
328
+ function showContainerTab(containerName) {
329
+ // Hide all container contents
330
+ const contents = document.querySelectorAll('.container-content');
331
+ contents.forEach(content => content.classList.remove('active'));
332
+
333
+ // Remove active class from all tabs
334
+ const tabs = document.querySelectorAll('.container-tab');
335
+ tabs.forEach(tab => tab.classList.remove('active'));
336
+
337
+ // Show selected container content
338
+ document.getElementById(`container_${containerName}`).classList.add('active');
339
+
340
+ // Add active class to clicked tab
341
+ event.target.classList.add('active');
342
+ }
343
+
344
+ function generateContainerContent(container) {
345
+ switch (container) {
346
+ case 'header':
347
+ return `
348
+ <h3>Header Configuration</h3>
349
+ <p>Configure the header section (banner, title, navigation).</p>
350
+ <label for="header_content">Header Content:</label>
351
+ <textarea name="header_content" placeholder="Enter header configuration (e.g., 'banner svg', 'title', 'title&#10;subtitle')...">banner svg</textarea>
352
+ <small class="form-text text-muted">Note: When using 'banner svg', the title and subtitle are automatically included from the view settings.</small>
353
+ `;
354
+ case 'main':
355
+ return `
356
+ <h3>Main Content Configuration</h3>
357
+ <p>Configure the main content area (usually populated by JavaScript with posts).</p>
358
+ <label for="main_content">Main Content:</label>
359
+ <textarea name="main_content" placeholder="Enter main content configuration..."># Main content (usually populated by JavaScript)</textarea>
360
+ `;
361
+ case 'left':
362
+ return `
363
+ <h3>Left Sidebar Configuration</h3>
364
+ <p>Configure the left sidebar content.</p>
365
+ <label for="left_content">Left Sidebar Content:</label>
366
+ <textarea name="left_content" placeholder="Enter left sidebar configuration..."># Left sidebar content</textarea>
367
+ `;
368
+ case 'right':
369
+ return `
370
+ <h3>Right Sidebar Configuration</h3>
371
+ <p>Configure the right sidebar content.</p>
372
+ <label for="right_content">Right Sidebar Content:</label>
373
+ <textarea name="right_content" placeholder="Enter right sidebar configuration..."># Right sidebar content</textarea>
374
+ `;
375
+ case 'footer':
376
+ return `
377
+ <h3>Footer Configuration</h3>
378
+ <p>Configure the footer section (copyright, links, etc.).</p>
379
+ <label for="footer_content">Footer Content:</label>
380
+ <textarea name="footer_content" placeholder="Enter footer configuration..."># Footer content</textarea>
381
+ `;
382
+ default:
383
+ return `<p>Configuration for ${container} container.</p>`;
384
+ }
385
+ }
386
+
387
+ // Event listeners for layout changes
388
+ document.addEventListener('DOMContentLoaded', function() {
389
+ const checkboxes = document.querySelectorAll('input[name="containers[]"]');
390
+ checkboxes.forEach(checkbox => {
391
+ checkbox.addEventListener('change', updateLayoutPreview);
392
+ });
393
+
394
+ const widthInputs = document.querySelectorAll('input[id$="_width"]');
395
+ widthInputs.forEach(input => {
396
+ input.addEventListener('input', updateLayoutPreview);
397
+ });
398
+ });
399
+ </script>
400
+ </body>
401
+ </html>
@@ -0,0 +1,154 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Scriptorium Web UI</title>
5
+ <style>
6
+ body { font-family: Arial, sans-serif; margin: 20px; }
7
+ .error { color: red; background: #ffe6e6; padding: 10px; margin: 10px 0; }
8
+ .message { color: green; background: #e6ffe6; padding: 10px; margin: 10px 0; }
9
+ .suggestion { color: #666; font-style: italic; }
10
+ table { border-collapse: collapse; width: 100%; margin: 10px 0; }
11
+ th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
12
+ th { background-color: #f2f2f2; }
13
+ .button { background: #007cba; color: white; padding: 5px 10px; text-decoration: none; border-radius: 3px; }
14
+ .button:hover { background: #005a87; }
15
+ .section { margin: 20px 0; }
16
+ .section h3 { margin-bottom: 10px; }
17
+ .posts-table { border-collapse: collapse; width: 100%; margin: 10px 0; }
18
+ .posts-table th, .posts-table td { padding: 4px 8px; text-align: left; border: none; }
19
+ .posts-table th { background-color: #f2f2f2; }
20
+ .posts-table .post-id { text-align: right !important; font-family: monospace; min-width: 40px; }
21
+ </style>
22
+ <script>
23
+ function ensureOverlay(id, text, bg) {
24
+ let el = document.getElementById(id);
25
+ if (!el) {
26
+ el = document.createElement('div');
27
+ el.id = id;
28
+ el.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 16px 22px; border-radius: 8px; z-index: 9999; color: white; font-weight: bold; box-shadow: 0 4px 12px rgba(0,0,0,0.3);';
29
+ document.body.appendChild(el);
30
+ }
31
+ el.style.background = bg;
32
+ el.textContent = text;
33
+ el.style.display = 'block';
34
+ return el;
35
+ }
36
+ function showGeneratingPopup() { ensureOverlay('gen-popup', 'Generating view...', '#007bff'); }
37
+ function showPreviewPopup() { ensureOverlay('preview-popup', 'Opening preview...', '#17a2b8'); }
38
+ function previewView(viewName) {
39
+ // Show lightweight popup while the new tab loads
40
+ try { showPreviewPopup(); } catch(e) {}
41
+ // Open preview in new tab with GET request
42
+ window.open('/preview', '_blank');
43
+ }
44
+
45
+ function changeView(viewName) {
46
+ // Submit form via AJAX to avoid URL change
47
+ const formData = new FormData();
48
+ formData.append('view_name', viewName);
49
+
50
+ fetch('/change_view', {
51
+ method: 'POST',
52
+ body: formData
53
+ })
54
+ .then(response => {
55
+ if (response.ok) {
56
+ // Reload the page to show the new view
57
+ window.location.reload();
58
+ } else {
59
+ alert('Failed to change view');
60
+ }
61
+ })
62
+ .catch(error => {
63
+ console.error('Error:', error);
64
+ alert('Error changing view');
65
+ });
66
+ }
67
+ </script>
68
+ </head>
69
+ <body>
70
+ <h1>Scriptorium Web UI</h1>
71
+
72
+ <% if @error %>
73
+ <div class="error">
74
+ <strong>Error:</strong> <%= @error %>
75
+ <% if @suggestion %>
76
+ <div class="suggestion"><%= @suggestion %></div>
77
+ <% end %>
78
+ </div>
79
+ <% end %>
80
+
81
+ <% if @message %>
82
+ <div class="message"><%= @message %></div>
83
+ <% end %>
84
+
85
+ <% if @api && @api.repo %>
86
+ <div class="section">
87
+ <h3>Repository loaded: <%= @api.repo.root %></h3>
88
+
89
+ <div class="section">
90
+ <h3>Views</h3>
91
+ <% if @views && !@views.empty? %>
92
+ <table>
93
+ <tr>
94
+ <th>Name <em style="font-size: 0.8em; font-weight: normal;">(Click to switch views)</em></th>
95
+ <th>Title <em style="font-size: 0.8em; font-weight: normal;">(Click to configure)</em></th>
96
+ </tr>
97
+ <% @views.each do |view| %>
98
+ <% if @current_view && view.name == @current_view.name %>
99
+ <!-- Current view - entire row clickable but does nothing -->
100
+ <tr style="background-color: #e3f2fd; cursor: pointer;" onclick="void(0)" title="Current view (clicking does nothing)">
101
+ <td style="color: #007cba; font-family: monospace;">
102
+ <%= view.name %>
103
+ </td>
104
+ <td style="color: #007cba;">
105
+ <%= view.title %> (Current)
106
+ </td>
107
+ </tr>
108
+ <% else %>
109
+ <!-- Other views - entire row clickable -->
110
+ <tr style="cursor: pointer;" onclick="changeView('<%= view.name %>')" title="Change to <%= view.name %>">
111
+ <td style="color: #007cba; font-family: monospace;">
112
+ <%= view.name %>
113
+ </td>
114
+ <td style="color: #007cba;">
115
+ <%= view.title %>
116
+ </td>
117
+ </tr>
118
+ <% end %>
119
+ <% end %>
120
+ </table>
121
+
122
+ <% if @current_view %>
123
+ <div style="margin-top: 15px; padding: 10px; background-color: #f0f8ff; border: 1px solid #b0d4f1; border-radius: 3px;">
124
+ <strong>Current view:</strong> <%= @current_view.name %>
125
+ <a href="/view/<%= @current_view.name %>" class="button" style="margin-left: 10px; text-decoration: none;">View Dashboard</a>
126
+ <a href="/asset_management" class="button" style="margin-left: 5px; text-decoration: none;">Asset Management</a>
127
+ <a href="/theme_management" class="button" style="margin-left: 5px; text-decoration: none;">Theme Management</a>
128
+ </div>
129
+ <% end %>
130
+ <% else %>
131
+ <p>No views found</p>
132
+ <% end %>
133
+
134
+ <form method="post" action="/create_view" style="margin-top: 10px;">
135
+ <input type="text" name="name" placeholder="View name" required>
136
+ <input type="text" name="title" placeholder="View title" required>
137
+ <input type="text" name="subtitle" placeholder="View subtitle" required>
138
+ <button type="submit" class="button">Create new view</button>
139
+ </form>
140
+ </div>
141
+
142
+
143
+ </div>
144
+ <% else %>
145
+ <div class="section">
146
+ <h3>No repository loaded</h3>
147
+ <form method="post" action="/create_repo">
148
+ <input type="text" name="name" placeholder="Repository name" value="scriptorium-TEST" required>
149
+ <button type="submit" class="button">Create Repository</button>
150
+ </form>
151
+ </div>
152
+ <% end %>
153
+ </body>
154
+ </html>