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,244 @@
1
+ // Custom LiveText syntax highlighting mode for CodeMirror
2
+ CodeMirror.defineMode("livetext", function() {
3
+ return {
4
+ startState: function() {
5
+ return {
6
+ inRaw: false,
7
+ inCodeBody: false,
8
+ codeLanguage: null,
9
+ inBracket: false,
10
+ bracketDepth: 0
11
+ };
12
+ },
13
+
14
+ token: function(stream, state) {
15
+ // Skip whitespace
16
+ if (stream.eatSpace()) return null;
17
+
18
+ // Raw blocks override everything else
19
+ if (state.inRaw) {
20
+ if (stream.match(/^__RAW__/) || stream.match(/^\.end$/)) {
21
+ state.inRaw = false;
22
+ return "keyword";
23
+ }
24
+ stream.skipToEnd();
25
+ return "comment"; // Plain text in raw blocks
26
+ }
27
+
28
+ // Check for .raw command
29
+ if (stream.match(/^\.raw$/)) {
30
+ state.inRaw = true;
31
+ return "keyword";
32
+ }
33
+
34
+ // Check for .comment command
35
+ if (stream.match(/^\.comment$/)) {
36
+ state.inRaw = true; // Use same state as raw for plain text
37
+ return "keyword";
38
+ }
39
+
40
+ // Code body blocks
41
+ if (state.inCodeBody) {
42
+ if (stream.match(/^\.end$/)) {
43
+ state.inCodeBody = false;
44
+ state.codeLanguage = null;
45
+ return "keyword";
46
+ }
47
+
48
+ // Use appropriate language highlighting
49
+ if (state.codeLanguage === "ruby") {
50
+ // Ruby syntax highlighting
51
+ if (stream.match(/^#/)) {
52
+ stream.skipToEnd();
53
+ return "comment";
54
+ }
55
+ if (stream.match(/^def\b/)) return "def";
56
+ if (stream.match(/^end\b/)) return "keyword";
57
+ if (stream.match(/^class\b/)) return "def";
58
+ if (stream.match(/^module\b/)) return "def";
59
+ if (stream.match(/^require\b/)) return "keyword";
60
+ if (stream.match(/^puts\b/)) return "keyword";
61
+ if (stream.match(/^[A-Z][a-zA-Z0-9_]*/)) return "variable-2"; // Constants
62
+ if (stream.match(/^[a-z_][a-zA-Z0-9_]*/)) return "variable"; // Variables
63
+ if (stream.match(/^[0-9]+/)) return "number";
64
+ if (stream.match(/^["']/)) {
65
+ stream.skipTo(/["']/);
66
+ return "string";
67
+ }
68
+ } else if (state.codeLanguage === "elixir") {
69
+ // Elixir syntax highlighting
70
+ if (stream.match(/^#/)) {
71
+ stream.skipToEnd();
72
+ return "comment";
73
+ }
74
+ if (stream.match(/^def\b/)) return "def";
75
+ if (stream.match(/^end\b/)) return "keyword";
76
+ if (stream.match(/^defmodule\b/)) return "def";
77
+ if (stream.match(/^defp\b/)) return "def";
78
+ if (stream.match(/^IO\.puts\b/)) return "keyword";
79
+ if (stream.match(/^[A-Z][a-zA-Z0-9_]*/)) return "variable-2"; // Modules
80
+ if (stream.match(/^[a-z_][a-zA-Z0-9_]*/)) return "keyword"; // Functions
81
+ if (stream.match(/^[0-9]+/)) return "number";
82
+ if (stream.match(/^["']/)) {
83
+ stream.skipTo(/["']/);
84
+ return "string";
85
+ }
86
+ } else if (state.codeLanguage === "bash") {
87
+ // Bash syntax highlighting
88
+ if (stream.match(/^#/)) {
89
+ stream.skipToEnd();
90
+ return "comment";
91
+ }
92
+ if (stream.match(/^if\b/)) return "keyword";
93
+ if (stream.match(/^then\b/)) return "keyword";
94
+ if (stream.match(/^fi\b/)) return "keyword";
95
+ if (stream.match(/^for\b/)) return "keyword";
96
+ if (stream.match(/^do\b/)) return "keyword";
97
+ if (stream.match(/^done\b/)) return "keyword";
98
+ if (stream.match(/^\$[A-Z_]+/)) return "variable"; // Environment variables
99
+ if (stream.match(/^[0-9]+/)) return "number";
100
+ if (stream.match(/^["']/)) {
101
+ stream.skipTo(/["']/);
102
+ return "string";
103
+ }
104
+ }
105
+
106
+ stream.next();
107
+ return null;
108
+ }
109
+
110
+ // Check for code body commands
111
+ if (stream.match(/^\.(def|func)$/)) {
112
+ state.inCodeBody = true;
113
+ state.codeLanguage = "ruby";
114
+ return "keyword";
115
+ }
116
+
117
+ let codeMatch = stream.match(/^\.code\s+(ruby|elixir|bash)/);
118
+ if (codeMatch) {
119
+ state.inCodeBody = true;
120
+ state.codeLanguage = codeMatch[1];
121
+ return "keyword";
122
+ }
123
+
124
+ // One-line comments (. comment)
125
+ if (stream.match(/^\.\s/)) {
126
+ stream.skipToEnd();
127
+ return "comment";
128
+ }
129
+
130
+ // Dot commands (.command or $.command)
131
+ if (stream.match(/^\.\w+/)) {
132
+ return "keyword";
133
+ }
134
+ if (stream.match(/^\s+\$\.[\w.]+/)) {
135
+ return "keyword";
136
+ }
137
+
138
+ // Variables ($foo, $foo.bar) - stop at invalid characters
139
+ if (stream.match(/^\$/)) {
140
+ let name = "";
141
+ let ch = stream.next();
142
+ if (ch && /[a-zA-Z]/.test(ch)) {
143
+ name += ch;
144
+ while (stream.peek() && /[\w.]/.test(stream.peek())) {
145
+ ch = stream.next();
146
+ name += ch;
147
+ }
148
+ // Check if next character is invalid for a name
149
+ if (stream.peek() && !/[\w.]/.test(stream.peek())) {
150
+ return "variable";
151
+ }
152
+ }
153
+ return "variable";
154
+ }
155
+
156
+ // Functions ($$foo, $$foo:param, $$foo[param])
157
+ if (stream.match(/^\$\$/)) {
158
+ let name = "";
159
+ let ch = stream.next();
160
+ if (ch && /[a-zA-Z]/.test(ch)) {
161
+ name += ch;
162
+ while (stream.peek() && /[\w.]/.test(stream.peek())) {
163
+ ch = stream.next();
164
+ name += ch;
165
+ }
166
+ // Check for colon parameter
167
+ if (stream.peek() === ":") {
168
+ stream.next();
169
+ stream.skipToEnd();
170
+ }
171
+ return "function";
172
+ }
173
+ return "function";
174
+ }
175
+
176
+ // Bracketed content [content]
177
+ if (stream.match(/^\[/)) {
178
+ state.inBracket = true;
179
+ state.bracketDepth = 1;
180
+ return "bracket";
181
+ }
182
+
183
+ if (state.inBracket) {
184
+ if (stream.match(/^\]/)) {
185
+ state.inBracket = false;
186
+ state.bracketDepth = 0;
187
+ return "bracket";
188
+ }
189
+ // Continue reading bracket content
190
+ stream.next();
191
+ return "string";
192
+ }
193
+
194
+ // Bracketed formatting: *[text] _[text] `[text] ~[text]
195
+ if (stream.match(/^(\*|_|`|~)\[/)) {
196
+ state.inBracket = true;
197
+ state.bracketDepth = 1;
198
+ return "strong"; // All bracketed content gets same treatment for now
199
+ }
200
+
201
+ // Markers (* _ ` ~) - single and doubled
202
+ // Note: LiveText markers only initiate, never terminate
203
+ if (stream.match(/^\*\*/)) {
204
+ // Doubled marker - read until space, comma, or period
205
+ stream.eatWhile(/[^\s,.]/);
206
+ return "strong";
207
+ }
208
+ if (stream.match(/^__/)) {
209
+ stream.eatWhile(/[^\s,.]/);
210
+ return "em";
211
+ }
212
+ if (stream.match(/^``/)) {
213
+ stream.eatWhile(/[^\s,.]/);
214
+ return "string-2";
215
+ }
216
+ if (stream.match(/^~~/)) {
217
+ stream.eatWhile(/[^\s,.]/);
218
+ return "strikethrough";
219
+ }
220
+
221
+ // Single markers
222
+ if (stream.match(/^\*/)) {
223
+ stream.eatWhile(/[^\s]/);
224
+ return "strong";
225
+ }
226
+ if (stream.match(/^_/)) {
227
+ stream.eatWhile(/[^\s]/);
228
+ return "em";
229
+ }
230
+ if (stream.match(/^`/)) {
231
+ stream.eatWhile(/[^\s]/);
232
+ return "string-2";
233
+ }
234
+ if (stream.match(/^~/)) {
235
+ stream.eatWhile(/[^\s]/);
236
+ return "strikethrough";
237
+ }
238
+
239
+ // Continue reading
240
+ stream.next();
241
+ return null;
242
+ }
243
+ };
244
+ });
@@ -0,0 +1,150 @@
1
+ module ErrorHelpers
2
+ # Error message mapping for common errors
3
+ ERROR_MESSAGES = {
4
+ 'ReadFileNotFound' => {
5
+ message: 'File not found',
6
+ suggestion: 'The file may have been moved or deleted. Try refreshing the page or recreating the content.'
7
+ },
8
+ 'PostRepoNil' => {
9
+ message: 'Repository not initialized',
10
+ suggestion: 'Please create or open a repository first.'
11
+ },
12
+ 'PostNumNil' => {
13
+ message: 'Invalid post ID',
14
+ suggestion: 'Please provide a valid post number.'
15
+ },
16
+ 'PostNumEmpty' => {
17
+ message: 'Post ID is empty',
18
+ suggestion: 'Please provide a valid post number.'
19
+ },
20
+ 'PostNumInvalid' => {
21
+ message: 'Invalid post ID format',
22
+ suggestion: 'Post ID must be a number.'
23
+ },
24
+ 'GetPostIdNil' => {
25
+ message: 'Post ID not provided',
26
+ suggestion: 'Please specify a post ID.'
27
+ },
28
+ 'GetPostIdEmpty' => {
29
+ message: 'Post ID is empty',
30
+ suggestion: 'Please provide a valid post ID.'
31
+ },
32
+ 'GetPostIdInvalid' => {
33
+ message: 'Invalid post ID format',
34
+ suggestion: 'Post ID must be a number.'
35
+ },
36
+ 'EditFilePathNil' => {
37
+ message: 'File path not specified',
38
+ suggestion: 'Please provide a valid file path.'
39
+ },
40
+ 'EditFilePathEmpty' => {
41
+ message: 'File path is empty',
42
+ suggestion: 'Please provide a valid file path.'
43
+ },
44
+ 'RepoDirAlreadyExists' => {
45
+ message: 'Repository already exists',
46
+ suggestion: 'The repository directory already exists. You can open it instead.'
47
+ },
48
+ 'LookupViewTargetNil' => {
49
+ message: 'View not specified',
50
+ suggestion: 'Please provide a valid view name.'
51
+ },
52
+ 'ViewNameNil' => {
53
+ message: 'View name not provided',
54
+ suggestion: 'Please provide a view name.'
55
+ },
56
+ 'ViewNameEmpty' => {
57
+ message: 'View name is empty',
58
+ suggestion: 'Please provide a view name.'
59
+ },
60
+ 'ViewTitleNil' => {
61
+ message: 'View title not provided',
62
+ suggestion: 'Please provide a view title.'
63
+ },
64
+ 'ViewTitleEmpty' => {
65
+ message: 'View title is empty',
66
+ suggestion: 'Please provide a view title.'
67
+ },
68
+ 'ViewNameInvalid' => {
69
+ message: 'Invalid view name',
70
+ suggestion: 'View names can only contain letters, numbers, hyphens (-), and underscores (_).'
71
+ }
72
+ }
73
+
74
+ # Get a user-friendly error message
75
+ def friendly_error_message(error)
76
+ error_class = error.class.name
77
+ error_message = error.message
78
+
79
+ if ERROR_MESSAGES[error_class]
80
+ {
81
+ message: ERROR_MESSAGES[error_class][:message],
82
+ suggestion: ERROR_MESSAGES[error_class][:suggestion],
83
+ details: error_message
84
+ }
85
+ else
86
+ {
87
+ message: 'An unexpected error occurred',
88
+ suggestion: 'Please try again or contact support if the problem persists.',
89
+ details: error_message
90
+ }
91
+ end
92
+ end
93
+
94
+ # Validate required parameters
95
+ def validate_required_params(params, *required_keys)
96
+ missing = required_keys.select { |key| params[key].nil? || params[key].to_s.strip.empty? }
97
+
98
+ if missing.any?
99
+ raise ArgumentError.new("Missing required parameters: #{missing.join(', ')}")
100
+ end
101
+ end
102
+
103
+ # Validate post ID
104
+ def validate_post_id(post_id)
105
+ return false if post_id.nil?
106
+ return false unless post_id.to_s.match?(/^\d+$/)
107
+ return false if post_id.to_i <= 0
108
+ true
109
+ end
110
+
111
+ # Validate view name
112
+ def validate_view_name(name)
113
+ return false if name.nil?
114
+ return false if name.to_s.strip.empty?
115
+ return false unless name.to_s.match?(/^[a-zA-Z0-9_-]+$/)
116
+ true
117
+ end
118
+
119
+ # Safe file operations
120
+ def safe_read_file(path, default_content = "")
121
+ return default_content unless File.exist?(path)
122
+ File.read(path)
123
+ rescue => e
124
+ puts "Error reading file #{path}: #{e.message}" if $DEBUG
125
+ default_content
126
+ end
127
+
128
+ def safe_write_file(path, content)
129
+ FileUtils.mkdir_p(File.dirname(path))
130
+ File.write(path, content)
131
+ true
132
+ rescue => e
133
+ puts "Error writing file #{path}: #{e.message}" if $DEBUG
134
+ false
135
+ end
136
+
137
+ # Format error for display
138
+ def format_error_display(error_info)
139
+ html = "<div class='error-details'>"
140
+ html += "<strong>#{error_info[:message]}</strong><br>"
141
+ html += "<em>#{error_info[:suggestion]}</em>"
142
+
143
+ if error_info[:details] && error_info[:details] != error_info[:message]
144
+ html += "<br><small>Technical details: #{error_info[:details]}</small>"
145
+ end
146
+
147
+ html += "</div>"
148
+ html
149
+ end
150
+ end
@@ -0,0 +1,196 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Advanced Configuration - <%= @current_view.name %> - Scriptorium</title>
5
+ <style>
6
+ body {
7
+ font-family: Arial, sans-serif;
8
+ margin: 0;
9
+ padding: 20px;
10
+ background-color: #f5f5f5;
11
+ }
12
+ .container {
13
+ max-width: 800px;
14
+ margin: 0 auto;
15
+ background: white;
16
+ border-radius: 8px;
17
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
18
+ overflow: hidden;
19
+ }
20
+ .header {
21
+ background: #007bff;
22
+ color: white;
23
+ padding: 20px;
24
+ text-align: center;
25
+ }
26
+ .header h1 {
27
+ margin: 0;
28
+ font-size: 24px;
29
+ }
30
+ .header p {
31
+ display: none;
32
+ }
33
+ .content {
34
+ padding: 30px;
35
+ }
36
+ .config-table {
37
+ width: 100%;
38
+ border-collapse: collapse;
39
+ }
40
+ .config-table td {
41
+ padding: 8px 0;
42
+ vertical-align: middle;
43
+ }
44
+ .config-table td:first-child {
45
+ width: 20px;
46
+ padding-right: 15px;
47
+ }
48
+ .config-table td:nth-child(2) {
49
+ width: 120px;
50
+ padding-right: 15px;
51
+ }
52
+ .config-table td:last-child {
53
+ padding-left: 0;
54
+ }
55
+ .config-item p {
56
+ margin: 0;
57
+ color: #666;
58
+ font-size: 14px;
59
+ line-height: 1.4;
60
+ flex: 1;
61
+ min-width: 0;
62
+ }
63
+ .btn {
64
+ display: inline-block;
65
+ padding: 8px 16px;
66
+ background: #007bff;
67
+ color: white;
68
+ text-decoration: none;
69
+ border-radius: 4px;
70
+ font-size: 14px;
71
+ transition: background 0.2s;
72
+ white-space: nowrap;
73
+ min-width: 120px;
74
+ text-align: center;
75
+ }
76
+ .btn {
77
+ display: inline-block;
78
+ padding: 8px 16px;
79
+ background: #007bff;
80
+ color: white;
81
+ text-decoration: none;
82
+ border-radius: 4px;
83
+ font-size: 14px;
84
+ transition: background 0.2s;
85
+ }
86
+ .btn:hover {
87
+ background: #0056b3;
88
+ }
89
+ .btn-secondary {
90
+ background: #6c757d;
91
+ }
92
+ .btn-secondary:hover {
93
+ background: #545b62;
94
+ }
95
+ .back-link {
96
+ margin-top: 20px;
97
+ text-align: center;
98
+ }
99
+ .status-indicator {
100
+ display: inline-block;
101
+ width: 8px;
102
+ height: 8px;
103
+ border-radius: 50%;
104
+ margin-right: 8px;
105
+ }
106
+ .status-active {
107
+ background: #28a745;
108
+ }
109
+ .status-inactive {
110
+ background: #dc3545;
111
+ }
112
+ </style>
113
+ </head>
114
+ <body>
115
+ <div class="container">
116
+ <div class="header">
117
+ <h1>Advanced Configuration</h1>
118
+ <p>Configure all aspects of <%= @current_view.name %></p>
119
+ </div>
120
+
121
+ <div class="content">
122
+ <table class="config-table">
123
+ <tr>
124
+ <td><span class="status-indicator status-active"></span></td>
125
+ <td><a href="#" class="btn">Update Main Settings</a></td>
126
+ <td>Configure basic view settings, title, subtitle, and other core properties.</td>
127
+ </tr>
128
+
129
+ <tr>
130
+ <td><span class="status-indicator status-active"></span></td>
131
+ <td><a href="/layout_config" class="btn">Edit Layout</a></td>
132
+ <td>Configure the overall page layout and structure.</td>
133
+ </tr>
134
+
135
+ <tr>
136
+ <td><span class="status-indicator <%= @configs[:header] ? 'status-active' : 'status-inactive' %>"></span></td>
137
+ <td><a href="/header_config" class="btn">Update Header</a></td>
138
+ <td>Set up the header structure and content for this view.</td>
139
+ </tr>
140
+
141
+ <% if @configs[:header] %>
142
+ <tr>
143
+ <td><span class="status-indicator <%= @configs[:banner] ? 'status-active' : 'status-inactive' %>"></span></td>
144
+ <td><a href="/banner_config" class="btn">Configure Banner</a></td>
145
+ <td>Customize the SVG banner with colors, fonts, and layout options.</td>
146
+ </tr>
147
+
148
+ <tr>
149
+ <td><span class="status-indicator <%= @configs[:navbar] ? 'status-active' : 'status-inactive' %>"></span></td>
150
+ <td><a href="/navbar_config" class="btn">Configure Navbar</a></td>
151
+ <td>Build the navigation menu structure with pages and sections.</td>
152
+ </tr>
153
+ <% end %>
154
+
155
+ <% if @layout_containers.include?('left') %>
156
+ <tr>
157
+ <td><span class="status-indicator status-active"></span></td>
158
+ <td><a href="#" class="btn">Update Left</a></td>
159
+ <td>Configure the left sidebar content and layout.</td>
160
+ </tr>
161
+ <% end %>
162
+
163
+ <% if @layout_containers.include?('right') %>
164
+ <tr>
165
+ <td><span class="status-indicator status-active"></span></td>
166
+ <td><a href="#" class="btn">Update Right</a></td>
167
+ <td>Configure the right sidebar content and layout.</td>
168
+ </tr>
169
+ <% end %>
170
+
171
+ <tr>
172
+ <td><span class="status-indicator status-active"></span></td>
173
+ <td><a href="/edit_pages" class="btn">Edit Pages</a></td>
174
+ <td>Create and edit content pages for this view.</td>
175
+ </tr>
176
+
177
+ <tr>
178
+ <td><span class="status-indicator status-active"></span></td>
179
+ <td><a href="/widgets" class="btn">Widget Management</a></td>
180
+ <td>Manage social media widgets and integrations for this view.</td>
181
+ </tr>
182
+
183
+ <tr>
184
+ <td><span class="status-indicator status-active"></span></td>
185
+ <td><a href="/deploy_config" class="btn">Configure Deployment</a></td>
186
+ <td>Set up server deployment settings for this view.</td>
187
+ </tr>
188
+ </table>
189
+
190
+ <div class="back-link">
191
+ <a href="/view/<%= @current_view.name %>" class="btn btn-secondary">Back to Dashboard</a>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </body>
196
+ </html>