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,149 @@
1
+ class Scriptorium::StandardFiles
2
+
3
+ # This class generates standard file content for new posts, views, etc.
4
+
5
+ include Scriptorium::Helpers
6
+
7
+ def initialize # remove?
8
+ end
9
+
10
+ def html_head_content(view = nil)
11
+ line1global = "# This global file supplies the default values for all views."
12
+ line2view = "# This view-specific file supplies the default values for this view."
13
+ line1 = view ? line2view : line1global
14
+ str = <<~EOS
15
+ #{line1}
16
+ # title is omitted - filled in at generation
17
+ charset UTF-8
18
+ desc A blog powered by Scriptorium. This is default text intended to be changed by the user.
19
+ viewport width=device-width initial-scale=1.0
20
+ robots index follow
21
+ javascript # See common.js
22
+ bootstrap # See bootstrap.txt
23
+ social # See social.txt for configuration
24
+ highlight # See highlight_css.txt for syntax highlighting
25
+ highlight_custom # Custom CSS overrides for Highlight.js
26
+ EOS
27
+ str
28
+ end
29
+
30
+ def common_js
31
+ files = [
32
+ 'common_js/navigation.js',
33
+ 'common_js/content-loader.js',
34
+ 'common_js/syntax-highlighting.js',
35
+ 'common_js/clipboard.js'
36
+ ]
37
+
38
+ files.map { |file| support_data(file) }.join("\n\n")
39
+ end
40
+
41
+
42
+
43
+ def highlight_ruby_js
44
+ <<~EOS
45
+ src https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/ruby.min.js
46
+ EOS
47
+ end
48
+
49
+ def available_widgets
50
+ <<~EOS
51
+ links
52
+ pages
53
+ featuredposts
54
+ EOS
55
+ end
56
+
57
+
58
+ def initial_post(mode = :filled, num: "0", title: nil, blurb: nil, views: nil,
59
+ tags: nil, body: nil)
60
+ # FIXME - screwed up?
61
+ title ||= "ADD TITLE HERE"
62
+ blurb ||= "ADD BLURB HERE"
63
+ views ||= %w[sample]
64
+ tags ||= %w[sample tags]
65
+ body ||= "BEGIN HERE..."
66
+
67
+ str = support_data('templates/initial_post.lt3')
68
+
69
+ return str if mode == :raw
70
+ mytags = tags
71
+ mytags = tags.join(", ") if tags.is_a?(Array)
72
+ str2 = str % {num: d4(num.to_i), created: ymdhms, title: title, blurb: blurb,
73
+ views: views.join(" "), tags: mytags, body: body}
74
+ return str2
75
+ end
76
+
77
+ def initial_post_metadata(num: "0", title: nil, blurb: nil, views: nil, tags: nil)
78
+ title ||= "ADD TITLE HERE"
79
+ blurb ||= "ADD BLURB HERE"
80
+ views ||= %w[sample]
81
+ tags ||= %w[sample tags]
82
+
83
+ mytags = tags
84
+ mytags = tags.join(", ") if tags.is_a?(Array)
85
+
86
+ <<~EOS
87
+ post.id #{d4(num.to_i)}
88
+ post.created #{ymdhms}
89
+ post.published no
90
+ post.deployed no
91
+ post.title #{title}
92
+ post.blurb #{blurb}
93
+ post.views #{views.join(" ")}
94
+ post.tags #{mytags}
95
+ EOS
96
+ end
97
+
98
+ def support_data(relative_path)
99
+ read_file(support_file(relative_path))
100
+ end
101
+
102
+ def post_template
103
+ support_data('templates/post.lt3')
104
+ end
105
+
106
+ def svg_txt
107
+ <<~EOS
108
+ aspect 7.0
109
+ text.font verdana
110
+ title.color #cccccc
111
+ subtitle.color #cccccc
112
+ text.justify left
113
+
114
+ title.scale 0.8
115
+ subtitle.scale 0.4
116
+
117
+ title.style bold
118
+ subtitle.style bold italic
119
+ title.xy 5 50
120
+ subtitle.xy 5 75
121
+
122
+ back.linear #0000cc #000077 lr
123
+
124
+
125
+ EOS
126
+ end
127
+
128
+ def deploy_text
129
+ <<~EOS
130
+ user root
131
+ server %{domain}
132
+ docroot /var/www/html
133
+ path %{view}
134
+ proto https
135
+ EOS
136
+ end
137
+
138
+ def status_txt
139
+ <<~EOS
140
+ header n
141
+ banner n
142
+ navbar n
143
+ left n
144
+ right n
145
+ pages n
146
+ deploy n
147
+ EOS
148
+ end
149
+ end
@@ -0,0 +1,5 @@
1
+ href https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
2
+ rel stylesheet
3
+ # integrity has a problem - compute instead?
4
+ # integrity sha384-KyZXEJ04F5o1v7V5b7ZMjGhGjxA8yQmBfvZwzI1r+0gEv+9KnQIMJxWwzD0u8nZ7
5
+ # crossorigin anonymous
@@ -0,0 +1,4 @@
1
+ src https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js
2
+ # integrity has a problem - compute instead?
3
+ # integrity sha384-pzjw8f+ua7Kw1TIq0+v5+GZkR6P/6v03cI0myXcJU22Hc5p5BY5/X93HmaJXjm4C
4
+ crossorigin anonymous
@@ -0,0 +1,35 @@
1
+ // Copy permalink to clipboard functionality
2
+ function copyPermalinkToClipboard(button) {
3
+ // Get the current post slug from the URL or construct it
4
+ const currentUrl = window.location.href;
5
+ let permalinkUrl;
6
+
7
+ if (currentUrl.includes('?post=')) {
8
+ // We're on the main blog page, construct the permalink URL
9
+ const postSlug = currentUrl.split('?post=')[1];
10
+ // const baseUrl = window.location.origin + window.location.pathname.replace(/\\[^\\/]*$/, '');
11
+ const parts = window.location.pathname.split('/');
12
+ parts.pop();
13
+ const baseUrl = window.location.origin + parts.join('/') + '/';
14
+ permalinkUrl = baseUrl + 'permalink/' + postSlug;
15
+ } else {
16
+ // We're already on a permalink page, use current URL
17
+ permalinkUrl = currentUrl;
18
+ }
19
+
20
+ navigator.clipboard.writeText(permalinkUrl).then(function() {
21
+ // Change button text temporarily to show success
22
+ const btn = button || document.activeElement || document.querySelector('button[onclick*="copyPermalinkToClipboard"]');
23
+ if (!btn) { return; }
24
+ const originalText = btn.textContent;
25
+ btn.textContent = 'Copied!';
26
+ btn.style.background = '#28a745';
27
+ setTimeout(function() {
28
+ btn.textContent = originalText;
29
+ btn.style.background = '#007bff';
30
+ }, 2000);
31
+ }).catch(function(err) {
32
+ console.error('Failed to copy: ', err);
33
+ alert('Failed to copy link to clipboard');
34
+ });
35
+ }
@@ -0,0 +1,187 @@
1
+ // Simple cache for index fragment to make Back instant in SPA
2
+ let __indexFragmentCache = null;
3
+
4
+ // Load the main content and other page containers (header, footer, left, right)
5
+ function load_main(slug) {
6
+ // (timing removed)
7
+ // Determine preview root (/preview/<view>/) for absolute fetches
8
+ const pathParts = window.location.pathname.split('/');
9
+ const previewIdx = pathParts.indexOf('preview');
10
+ const viewName = (previewIdx >= 0 && pathParts.length > previewIdx + 1) ? pathParts[previewIdx + 1] : '';
11
+ const previewRoot = `/preview/${viewName}/`;
12
+ // Get all container elements (header, footer, left, right, and main)
13
+ const contentDiv = document.getElementById("main");
14
+ const headerDiv = document.querySelector("header");
15
+ const footerDiv = document.querySelector("footer");
16
+ const leftDiv = document.querySelector(".left");
17
+ const rightDiv = document.querySelector(".right");
18
+ console.log('Loading main with slug:', slug); // Log the slug
19
+
20
+ // Clear any existing content to prevent nesting
21
+ contentDiv.innerHTML = '';
22
+ contentDiv.dataset.currentSlug = slug;
23
+
24
+ // Check if this is a post parameter request
25
+ if (slug.includes('?post=')) {
26
+ const postSlug = slug.split('?post=')[1];
27
+ console.log('Loading post:', postSlug);
28
+
29
+ // Load the post content from preview root
30
+ const postFile = postSlug.endsWith('.html') ? postSlug : postSlug + '.html';
31
+ const postUrl = previewRoot + 'posts/' + postFile;
32
+ console.log('Fetching post file:', postUrl);
33
+ // Proactively prefetch index fragment to make Back instant
34
+ if (!__indexFragmentCache) {
35
+ fetch(previewRoot + 'post_index.html').then(r => r.ok ? r.text() : '').then(t => { if (t) { __indexFragmentCache = t; } }).catch(() => {});
36
+ }
37
+ fetch(postUrl)
38
+ .then(response => {
39
+ console.log('Response status:', response.status);
40
+ if (response.ok) {
41
+ return response.text();
42
+ } else {
43
+ console.error('Failed to load post:', response.status);
44
+ return 'Post not found';
45
+ }
46
+ })
47
+ .then(content => {
48
+ console.log('Loaded content length:', content.length);
49
+ console.log('Content preview:', content.substring(0, 200));
50
+ contentDiv.innerHTML = content;
51
+ // Intercept Back link to return to index within SPA
52
+ try {
53
+ contentDiv.querySelectorAll('a[href]').forEach(a => {
54
+ const href = a.getAttribute('href') || '';
55
+ if (href === 'index.html' || href.endsWith('/index.html') || href === '../index.html') {
56
+ a.addEventListener('click', function(ev) {
57
+ ev.preventDefault();
58
+ // Fast path: render cached index if available
59
+ if (__indexFragmentCache) {
60
+ contentDiv.innerHTML = __indexFragmentCache;
61
+ history.pushState({slug: 'index.html'}, "", 'index.html');
62
+ } else {
63
+ load_main('index.html');
64
+ }
65
+ });
66
+ }
67
+ });
68
+ } catch (e) { console.warn('Back link interception skipped:', e); }
69
+ // Fix relative asset URLs (e.g., ../assets or ../../assets) to preview route
70
+ try {
71
+ const parts = window.location.pathname.split('/');
72
+ const idx = parts.indexOf('preview');
73
+ const viewName = idx >= 0 && parts.length > idx + 1 ? parts[idx + 1] : '';
74
+ const baseAssets = `/preview/${viewName}/assets/`;
75
+ // Update img[src]
76
+ contentDiv.querySelectorAll('img[src]').forEach(img => {
77
+ const s = img.getAttribute('src');
78
+ const m = s && s.match(/^(?:\.\.\/)+assets\/(.*)$/);
79
+ if (m) { img.setAttribute('src', baseAssets + m[1]); }
80
+ });
81
+ // Update link[href] (e.g., styles or anchors that point into assets)
82
+ contentDiv.querySelectorAll('a[href], link[href]').forEach(a => {
83
+ const s = a.getAttribute('href');
84
+ const m = s && s.match(/^(?:\.\.\/)+assets\/(.*)$/);
85
+ if (m) { a.setAttribute('href', baseAssets + m[1]); }
86
+ });
87
+ // Update script[src]
88
+ contentDiv.querySelectorAll('script[src]').forEach(sc => {
89
+ const s = sc.getAttribute('src');
90
+ const m = s && s.match(/^(?:\.\.\/)+assets\/(.*)$/);
91
+ if (m) { sc.setAttribute('src', baseAssets + m[1]); }
92
+ });
93
+ } catch (e) {
94
+ console.warn('Asset URL rewrite skipped:', e);
95
+ }
96
+
97
+ // Debug: Check what pre elements exist
98
+ const allPreElements = contentDiv.querySelectorAll('pre');
99
+ console.log('All pre elements found:', allPreElements.length);
100
+ allPreElements.forEach((pre, index) => {
101
+ console.log(`Pre element ${index}:`, pre.outerHTML.substring(0, 100));
102
+ });
103
+
104
+ // Highlight the newly loaded content
105
+ highlightNewContent(contentDiv);
106
+
107
+ // (detailed timing removed)
108
+
109
+ history.pushState({slug: 'index.html?post=' + postSlug}, "", 'index.html?post=' + postSlug);
110
+ // (timing removed)
111
+ })
112
+ .catch(error => {
113
+ console.log("Error loading post:", error);
114
+ contentDiv.innerHTML = 'Error loading post';
115
+ });
116
+ return;
117
+ }
118
+
119
+ // Check if this is a static page request (pages/, assets/, etc.)
120
+ if (slug.startsWith('pages/') || slug.startsWith('assets/') || slug.includes('/')) {
121
+ console.log('Loading static page:', slug);
122
+ // Fetch from preview root to avoid base href side-effects
123
+ fetch(previewRoot + slug)
124
+ .then(response => {
125
+ if (response.ok) {
126
+ return response.text();
127
+ } else {
128
+ console.error('Failed to load static page:', response.status);
129
+ return 'Page not found';
130
+ }
131
+ })
132
+ .then(content => {
133
+ contentDiv.innerHTML = content;
134
+ // Don't change the URL for static pages to avoid path resolution issues
135
+ history.pushState({slug: slug}, "", window.location.pathname);
136
+ })
137
+ .catch(error => {
138
+ console.log("Error loading static page:", error);
139
+ contentDiv.innerHTML = 'Error loading page';
140
+ });
141
+ return;
142
+ }
143
+
144
+ // If asked for index.html, prefer cached post index fragment for instant load
145
+ if (slug === 'index.html' && __indexFragmentCache) {
146
+ contentDiv.innerHTML = __indexFragmentCache;
147
+ history.pushState({slug: slug}, "", 'index.html');
148
+ return;
149
+ }
150
+
151
+ // Otherwise, fetch target (index fragment or other resource)
152
+ const target = (slug === 'index.html') ? (previewRoot + 'post_index.html') : (previewRoot + slug);
153
+ fetch(target)
154
+ .then(response => {
155
+ if (response.ok) {
156
+ console.log('Response is ok');
157
+ return response.text();
158
+ } else {
159
+ console.error('Failed to load:', response.status); // Log the failed response
160
+ }
161
+ })
162
+ .then(content => {
163
+ console.log('Loaded content into div'); // Log successful content insertion
164
+
165
+ // Inject into main container only
166
+ contentDiv.innerHTML = content;
167
+ if (slug === 'index.html' && content) { __indexFragmentCache = content; }
168
+
169
+ // Re-insert header, footer, left, and right (if necessary)
170
+ // If you want the static layout to be kept, you can preserve these parts
171
+ // with additional logic or predefined structure (here it's assumed
172
+ // that header/footer/left/right are already statically included).
173
+
174
+ // You can also replace the other parts (left, right, header, footer) if needed.
175
+ // Keep URL stable at index.html for index view, otherwise reflect the slug
176
+ const newUrl = (slug === 'index.html') ? 'index.html' : slug;
177
+
178
+ // Detailed timing for index fragment
179
+ // (detailed timing removed)
180
+
181
+ history.pushState({slug: slug}, "", newUrl);
182
+ // (timing removed)
183
+ })
184
+ .catch(error => {
185
+ console.log("Error loading content:", error); // Log any errors during fetch
186
+ });
187
+ }
@@ -0,0 +1,52 @@
1
+ // Handle the back/forward buttons with SPA-aware loading
2
+ window.onpopstate = function(event) {
3
+ try {
4
+ console.log('onpopstate event:', event);
5
+ const state = event.state || {};
6
+ const slug = state.slug || 'index.html';
7
+ // Use SPA loader to restore prior view instead of reloading
8
+ if (typeof load_main === 'function') {
9
+ load_main(slug);
10
+ return;
11
+ }
12
+ } catch (e) {
13
+ console.warn('onpopstate handler failed, reloading:', e);
14
+ }
15
+ // Fallback
16
+ window.location.reload();
17
+ };
18
+
19
+ // Initialize with the front page when navigating via the back button or similar
20
+ window.onload = function() {
21
+ // If under preview, set a base href so relative assets in injected posts resolve correctly
22
+ try {
23
+ const parts = window.location.pathname.split('/');
24
+ const idx = parts.indexOf('preview');
25
+ if (idx >= 0 && parts.length > idx + 1) {
26
+ const viewName = parts[idx + 1];
27
+ const base = document.createElement('base');
28
+ // Base at /preview/<view>/ so relative paths behave consistently with production
29
+ base.setAttribute('href', `/preview/${viewName}/`);
30
+ document.head.insertBefore(base, document.head.firstChild);
31
+ }
32
+ } catch (e) { console.warn('Base href insertion skipped:', e); }
33
+
34
+ // Check if the URL has a post parameter and load it automatically
35
+ const urlParams = new URLSearchParams(window.location.search);
36
+ const postParam = urlParams.get('post');
37
+
38
+ if (postParam) {
39
+ // URL has a post parameter, load the post content
40
+ console.log('Auto-loading post from URL parameter:', postParam);
41
+ load_main('index.html?post=' + postParam);
42
+ return;
43
+ }
44
+
45
+ // Check if the initial state exists, if not, set it
46
+ if (!history.state) {
47
+ // Don't try to load post_index.html if there are no posts
48
+ // The "No posts yet!" message is already in the main container
49
+ history.replaceState({ slug: "index.html" }, "", "index.html");
50
+ }
51
+ // (timing removed)
52
+ };
@@ -0,0 +1,27 @@
1
+ // Highlight newly loaded content
2
+ function highlightNewContent(container) {
3
+ if (typeof hljs !== 'undefined') {
4
+ // Find all code blocks in the new content and highlight them
5
+ const codeBlocks = container.querySelectorAll('pre code[class*="language-"]');
6
+ console.log('Found code blocks:', codeBlocks.length);
7
+ if (codeBlocks.length > 0) {
8
+ // Highlight each code block
9
+ codeBlocks.forEach((codeBlock, index) => {
10
+ console.log(`Highlighting code block ${index}:`, codeBlock);
11
+ try {
12
+ hljs.highlightElement(codeBlock);
13
+ console.log(`Successfully highlighted code block ${index}`);
14
+ } catch (error) {
15
+ console.error(`Error highlighting code block ${index}:`, error);
16
+ }
17
+ });
18
+ }
19
+ } else {
20
+ console.log('hljs is not defined');
21
+ }
22
+ }
23
+
24
+ // Initialize highlight.js syntax highlighting
25
+ if (typeof hljs !== 'undefined') {
26
+ hljs.highlightAll();
27
+ }
@@ -0,0 +1,10 @@
1
+ # Reddit sharing button configuration
2
+ # Set to true to show Reddit share button on posts
3
+ button true
4
+
5
+ # Optional: specify a subreddit for direct posting
6
+ # Leave empty or omit to let users choose subreddit
7
+ subreddit
8
+
9
+ # Optional: custom hover text (defaults to "Share on Reddit" or "Share on [subreddit]")
10
+ hover_text
@@ -0,0 +1,17 @@
1
+ # Reddit config data
2
+
3
+ # App information
4
+
5
+ user_agent Scriptorium Blog Bot 1.0 by /u/hal_fulton
6
+ client_id 7UpmDm8vg6v-mzuwyGcSUg
7
+ secret BsIXzKihItxEP6PtxGVl9ThXi4qEYw
8
+
9
+ # Your reddit login
10
+
11
+ username YOUR_REDDIT_USERNAME
12
+ password YOUR_REDDIT_PASSWORD
13
+
14
+ # Community (subreddit) where you'll post
15
+
16
+ subreddit YOUR_SUBREDDIT_NAME
17
+
@@ -0,0 +1,8 @@
1
+ # Available platforms: facebook twitter linkedin reddit
2
+ # List one platform per line to enable social meta tags
3
+ facebook
4
+ twitter
5
+ reddit
6
+
7
+ # Note: No Facebook App ID or Twitter username required for basic meta tags
8
+ # These are only needed if you want to add social sharing buttons later
@@ -0,0 +1,2 @@
1
+ href https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css
2
+ rel stylesheet
@@ -0,0 +1,119 @@
1
+ <style>
2
+ pre {
3
+ max-height: none !important;
4
+ height: auto !important;
5
+ overflow: visible !important;
6
+ background: #f5f2f0 !important;
7
+ padding: 1em !important;
8
+ margin: 1em 0 !important;
9
+ border-radius: 0.3em !important;
10
+ display: block !important;
11
+ }
12
+ pre code {
13
+ white-space: pre-wrap !important;
14
+ word-wrap: break-word !important;
15
+ display: block !important;
16
+ }
17
+ pre code tt {
18
+ white-space: normal !important;
19
+ display: inline !important;
20
+ }
21
+ code {
22
+ white-space: normal;
23
+ display: inline;
24
+ }
25
+ /* Normal whitespace handling */
26
+ #main {
27
+ white-space: normal;
28
+ }
29
+ #main tt {
30
+ white-space: nowrap !important;
31
+ display: inline !important;
32
+ float: none !important;
33
+ clear: none !important;
34
+ }
35
+ tt {
36
+ white-space: nowrap !important;
37
+ display: inline !important;
38
+ float: none !important;
39
+ clear: none !important;
40
+ }
41
+ .main tt {
42
+ white-space: nowrap !important;
43
+ display: inline !important;
44
+ float: none !important;
45
+ clear: none !important;
46
+ }
47
+ p tt {
48
+ white-space: nowrap !important;
49
+ display: inline !important;
50
+ float: none !important;
51
+ clear: none !important;
52
+ }
53
+ * tt {
54
+ white-space: nowrap !important;
55
+ display: inline !important;
56
+ float: none !important;
57
+ clear: none !important;
58
+ }
59
+ body tt {
60
+ white-space: nowrap !important;
61
+ display: inline !important;
62
+ float: none !important;
63
+ clear: none !important;
64
+ }
65
+ html tt {
66
+ white-space: nowrap !important;
67
+ display: inline !important;
68
+ float: none !important;
69
+ clear: none !important;
70
+ }
71
+ p tt {
72
+ white-space: nowrap !important;
73
+ display: inline !important;
74
+ float: none !important;
75
+ clear: none !important;
76
+ }
77
+ /* Normal paragraph styling - Bootstrap isolation not needed with proper HTML */
78
+ #main p {
79
+ margin: 0 0 1em 0;
80
+ }
81
+ #main p:last-child {
82
+ margin-bottom: 0;
83
+ }
84
+ p:only-child {
85
+ display: inline !important;
86
+ }
87
+ p tt {
88
+ white-space: nowrap !important;
89
+ display: inline !important;
90
+ float: none !important;
91
+ clear: none !important;
92
+ }
93
+ /* More specific selectors to override Bootstrap */
94
+ #main p tt {
95
+ white-space: nowrap !important;
96
+ display: inline !important;
97
+ float: none !important;
98
+ clear: none !important;
99
+ }
100
+ body #main p tt {
101
+ white-space: nowrap !important;
102
+ display: inline !important;
103
+ float: none !important;
104
+ clear: none !important;
105
+ }
106
+ html body #main p tt {
107
+ white-space: nowrap !important;
108
+ display: inline !important;
109
+ float: none !important;
110
+ clear: none !important;
111
+ }
112
+ /* Ensure br elements work for paragraph spacing */
113
+ br {
114
+ display: block !important;
115
+ margin: 0 !important;
116
+ padding: 0 !important;
117
+ line-height: 1.2em !important;
118
+ }
119
+ </style>
@@ -0,0 +1 @@
1
+ src https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js
@@ -0,0 +1,15 @@
1
+ posts.per.page 10
2
+ entry.title.size 1.1em
3
+ entry.blurb.size 0.75em
4
+ entry.date.size 0.7em
5
+ entry.date.width 14%
6
+ entry.cellpadding 4
7
+ entry.margin.bottom 6px
8
+ entry.title.color #000000
9
+ entry.blurb.color #666666
10
+ entry.date.color #888888
11
+ entry.line.height 1.1
12
+ entry.date.alignment right
13
+ entry.date.spacing 0.1em
14
+ entry.date.format month dd, yyyy
15
+ index.margin.top 20px