scriptorium 0.6.1 → 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 (358) hide show
  1. checksums.yaml +4 -4
  2. data/assets/icons/social/reddit.png +0 -0
  3. data/assets/icons/social/x-logo.png +0 -0
  4. data/assets/imagenotfound.jpg +0 -0
  5. data/bin/sblog +84 -5
  6. data/bin/scriptorium +1 -0
  7. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +0 -1
  8. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +0 -29
  9. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +0 -19
  10. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +1 -1
  11. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +1 -1
  12. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +1 -1
  13. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +0 -10
  14. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +1 -4
  15. data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
  16. data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
  17. data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
  18. data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
  19. data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
  20. data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
  21. data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
  22. data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
  23. data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
  24. data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
  25. data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
  26. data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
  27. data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
  28. data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
  29. data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
  30. data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
  31. data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
  32. data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
  33. data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
  34. data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
  35. data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
  36. data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
  37. data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
  38. data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
  39. data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
  40. data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
  41. data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
  42. data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
  43. data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
  44. data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
  45. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
  46. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
  47. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
  48. data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
  49. data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
  50. data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
  51. data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
  52. data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
  53. data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
  54. data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
  55. data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
  56. data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
  57. data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
  58. data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
  59. data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
  60. data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
  61. data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
  62. data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
  63. data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
  64. data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
  65. data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
  66. data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
  67. data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
  68. data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
  69. data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
  70. data/doc/imported/0018-ruby-puns/post.html +46 -0
  71. data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
  72. data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
  73. data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
  74. data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
  75. data/doc/imported/0020-fffff/metadata.txt +7 -0
  76. data/doc/imported/0020-fffff/post.html +24 -0
  77. data/doc/imported/0020-fffff/source.lt3 +12 -0
  78. data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
  79. data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
  80. data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
  81. data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
  82. data/doc/imported/0023-doctor-sleep/post.html +63 -0
  83. data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
  84. data/doc/imported/0024-just-a-test/metadata.txt +7 -0
  85. data/doc/imported/0024-just-a-test/post.html +24 -0
  86. data/doc/imported/0024-just-a-test/source.lt3 +12 -0
  87. data/doc/imported/import_summary.txt +98 -0
  88. data/doc/livetext-informal-spec.txt +65 -0
  89. data/doc/myuserdoc/ch-0.lt3 +31 -0
  90. data/doc/myuserdoc/ch-1.lt3 +37 -0
  91. data/doc/myuserdoc/ch-10.lt3 +22 -0
  92. data/doc/myuserdoc/ch-2.lt3 +37 -0
  93. data/doc/myuserdoc/ch-3.lt3 +19 -0
  94. data/doc/myuserdoc/ch-4.lt3 +43 -0
  95. data/doc/myuserdoc/ch-5.lt3 +22 -0
  96. data/doc/myuserdoc/ch-6.lt3 +19 -0
  97. data/doc/myuserdoc/ch-7.lt3 +16 -0
  98. data/doc/myuserdoc/ch-8.lt3 +13 -0
  99. data/doc/myuserdoc/ch-9.lt3 +19 -0
  100. data/doc/myuserdoc/tweak.rb +18 -0
  101. data/doc/{userdoc-toc.txt → myuserdoc/userdoc-toc.txt} +27 -27
  102. data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
  103. data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
  104. data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
  105. data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
  106. data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
  107. data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
  108. data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
  109. data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
  110. data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
  111. data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
  112. data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
  113. data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
  114. data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
  115. data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
  116. data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
  117. data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
  118. data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
  119. data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
  120. data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
  121. data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
  122. data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
  123. data/doc/old-posts/0024-just-a-test.lt3 +12 -0
  124. data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
  125. data/doc/old-repo +1 -0
  126. data/doc/reddit_integration.md +2 -2
  127. data/doc/user.lt3 +0 -3
  128. data/lib/scriptorium/api.rb +1811 -78
  129. data/lib/scriptorium/banner_svg.rb +55 -68
  130. data/lib/scriptorium/contract.rb +3 -2
  131. data/lib/scriptorium/exceptions.rb +133 -102
  132. data/lib/scriptorium/helpers.rb +282 -82
  133. data/lib/scriptorium/post.rb +81 -17
  134. data/lib/scriptorium/reddit.rb +1 -1
  135. data/lib/scriptorium/repo.rb +478 -164
  136. data/lib/scriptorium/standard_files.rb +30 -396
  137. data/lib/scriptorium/support/common_js/clipboard.js +35 -0
  138. data/lib/scriptorium/support/common_js/content-loader.js +187 -0
  139. data/lib/scriptorium/support/common_js/navigation.js +52 -0
  140. data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
  141. data/lib/scriptorium/support/config/reddit_template.txt +17 -0
  142. data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/social.txt +1 -0
  143. data/lib/scriptorium/support/highlight/css.txt +2 -0
  144. data/lib/scriptorium/support/highlight/custom.css +119 -0
  145. data/lib/scriptorium/support/highlight/js.txt +1 -0
  146. data/lib/scriptorium/support/post_index/config.txt +15 -0
  147. data/lib/scriptorium/support/post_index/style.css +55 -0
  148. data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
  149. data/{test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 → lib/scriptorium/support/templates/initial_post.lt3} +5 -5
  150. data/lib/scriptorium/support/templates/post.lt3 +104 -0
  151. data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt → lib/scriptorium/support/theme/header.lt3} +1 -1
  152. data/lib/scriptorium/theme.rb +83 -70
  153. data/lib/scriptorium/version.rb +2 -2
  154. data/lib/scriptorium/view.rb +194 -149
  155. data/lib/scriptorium.rb +24 -1
  156. data/lib/skeleton.rb +4 -1
  157. data/scriptorium.gemspec +2 -1
  158. data/test/WEB_INTEGRATION_README.md +196 -0
  159. data/test/all +40 -0
  160. data/test/banner_svg/unit.rb +267 -35
  161. data/test/config/deployment.txt +5 -0
  162. data/test/integration/integration_test.rb +7 -7
  163. data/test/integration/preview_flow_test.rb +94 -0
  164. data/test/livetext_plugin_test.rb +453 -182
  165. data/test/manual/banner-tests/test01.html +82 -18
  166. data/test/manual/banner-tests/test02.html +82 -18
  167. data/test/manual/banner-tests/test03.html +82 -18
  168. data/test/manual/banner-tests/test04.html +89 -25
  169. data/test/manual/banner-tests/test05.html +89 -25
  170. data/test/manual/banner-tests/test06.html +89 -25
  171. data/test/manual/banner-tests/test07.html +89 -25
  172. data/test/manual/banner-tests/test08.html +82 -18
  173. data/test/manual/banner-tests/test09.html +82 -18
  174. data/test/manual/banner-tests/test10.html +82 -18
  175. data/test/manual/banner-tests/test11.html +82 -18
  176. data/test/manual/banner-tests/test12.html +82 -18
  177. data/test/manual/banner-tests/test13.html +82 -18
  178. data/test/manual/banner-tests/test14.html +82 -18
  179. data/test/manual/banner-tests/test15.html +82 -18
  180. data/test/manual/banner-tests/test16.html +82 -18
  181. data/test/manual/banner-tests/test17.html +82 -18
  182. data/test/manual/banner-tests/test18.html +90 -26
  183. data/test/manual/banner-tests/test19.html +90 -26
  184. data/test/manual/banner-tests/test20.html +90 -26
  185. data/test/manual/banner-tests/test21.html +90 -26
  186. data/test/manual/banner-tests/test22.html +90 -26
  187. data/test/manual/banner-tests/test23.html +90 -26
  188. data/test/manual/banner-tests/test24.html +90 -26
  189. data/test/manual/banner-tests/test25.html +89 -25
  190. data/test/manual/banner_environment.rb +15 -2
  191. data/test/manual/codemirror_demo.html +773 -0
  192. data/test/manual/create_posts_for_web.rb +114 -0
  193. data/test/manual/preview_manual_test.rb +129 -0
  194. data/test/manual/test_banner_features.rb +14 -14
  195. data/test/manual/test_banner_integration.rb +115 -0
  196. data/test/manual/test_banner_radial.rb +87 -0
  197. data/test/manual/test_syntax_highlighting.rb +60 -40
  198. data/test/support/preview_utils.rb +88 -0
  199. data/test/test_gem_assets.rb +48 -0
  200. data/test/test_helpers.rb +10 -0
  201. data/test/tui_editor_integration_test.rb +15 -15
  202. data/test/tui_integration_test.rb +687 -441
  203. data/test/unit/api.rb +757 -37
  204. data/test/unit/asset_management.rb +195 -221
  205. data/test/unit/backup_test.rb +451 -0
  206. data/test/unit/contract_test.rb +1 -23
  207. data/test/unit/core.rb +415 -61
  208. data/test/unit/deploy_config_test.rb +248 -0
  209. data/test/unit/deploy_test.rb +312 -21
  210. data/test/unit/edit_post_test.rb +168 -0
  211. data/test/unit/gem_asset_management.rb +36 -42
  212. data/test/unit/livetext_basic.rb +23 -35
  213. data/test/unit/livetext_compatibility.rb +7 -14
  214. data/test/unit/parse_cmd_test.rb +260 -0
  215. data/test/unit/{symlink_test.rb → permalink_copy_test.rb} +47 -49
  216. data/test/unit/post.rb +91 -26
  217. data/test/unit/post_index_config_test.rb +258 -0
  218. data/test/unit/post_state_helpers_test.rb +137 -0
  219. data/test/unit/read_commented_file_test.rb +8 -6
  220. data/test/unit/repo.rb +75 -54
  221. data/test/unit/social_test.rb +41 -44
  222. data/test/unit/syntax_highlighting.rb +70 -0
  223. data/test/unit/theme_management_test.rb +91 -0
  224. data/test/unit/view.rb +79 -12
  225. data/test/unit/widgets.rb +8 -8
  226. data/test/web_integration_test.rb +231 -0
  227. data/test/web_test_helper.rb +218 -0
  228. data/test/web_workflow_test.rb +527 -0
  229. data/ui/tui/bin/scriptorium +885 -415
  230. data/ui/web/app/app.rb +1398 -176
  231. data/ui/web/app/assets/livetext_mode.js +244 -0
  232. data/ui/web/app/error_helpers.rb +16 -16
  233. data/ui/web/app/views/advanced_config.erb +8 -2
  234. data/ui/web/app/views/asset_management.erb +56 -0
  235. data/ui/web/app/views/backup_management.erb +238 -0
  236. data/ui/web/app/views/config_widget.erb +232 -0
  237. data/ui/web/app/views/dashboard.erb +64 -72
  238. data/ui/web/app/views/deploy_config.erb +3 -0
  239. data/ui/web/app/views/edit_pages.erb +170 -2
  240. data/ui/web/app/views/edit_post.erb +130 -9
  241. data/ui/web/app/views/edit_theme.erb +73 -0
  242. data/ui/web/app/views/edit_theme_file.erb +74 -0
  243. data/ui/web/app/views/theme_management.erb +130 -0
  244. data/ui/web/app/views/view_dashboard.erb +666 -25
  245. data/ui/web/app/views/widgets.erb +249 -0
  246. data/ui/web/bin/scriptorium-web +35 -24
  247. data/ui/web/tmp/timing.log +17 -0
  248. data/ui/web/tmp/web_server.log +0 -5
  249. metadata +190 -116
  250. data/assets/back-icon.png +0 -0
  251. data/assets/icons/facebook.svg +0 -1
  252. data/assets/icons/github.svg +0 -1
  253. data/assets/icons/instagram.svg +0 -1
  254. data/assets/icons/reddit.svg +0 -1
  255. data/assets/icons/x.svg +0 -1
  256. data/assets/icons/youtube.svg +0 -1
  257. data/bin/scriptorium +0 -1511
  258. data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +0 -34
  259. data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +0 -50
  260. data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +0 -73
  261. data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +0 -70
  262. data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +0 -110
  263. data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +0 -73
  264. data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +0 -124
  265. data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +0 -46
  266. data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +0 -97
  267. data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +0 -211
  268. data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +0 -141
  269. data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +0 -211
  270. data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +0 -134
  271. data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +0 -41
  272. data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +0 -49
  273. data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +0 -46
  274. data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +0 -41
  275. data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +0 -41
  276. data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +0 -256
  277. data/lib/scriptorium/syntax_highlighter.rb +0 -234
  278. data/test/manual/deploy_symlink_demo.rb +0 -142
  279. data/test/manual/symlink_demo.rb +0 -117
  280. data/test/manual/test2.rb +0 -12
  281. data/test/manual/test_banner_from_file.rb +0 -150
  282. data/test/manual/test_banner_in_header.rb +0 -35
  283. data/test/manual/test_code_highlighting.rb +0 -68
  284. data/test/manual/test_complex_header.rb +0 -74
  285. data/test/manual/test_empty_header.rb +0 -32
  286. data/test/manual/test_radial_custom.rb +0 -58
  287. data/test/manual/test_radial_large_radius.rb +0 -52
  288. data/test/manual/test_svg_debug.rb +0 -47
  289. data/test/pages-demo/config/currentview.txt +0 -1
  290. data/test/pages-demo/views/demo/config/common.js +0 -57
  291. data/test/pages-demo/views/demo/config/footer.txt +0 -1
  292. data/test/pages-demo/views/demo/config/global-head.txt +0 -8
  293. data/test/pages-demo/views/demo/config/header.txt +0 -1
  294. data/test/pages-demo/views/demo/config/layout.txt +0 -1
  295. data/test/pages-demo/views/demo/config/left.txt +0 -1
  296. data/test/pages-demo/views/demo/config/main.txt +0 -1
  297. data/test/pages-demo/views/demo/config/right.txt +0 -1
  298. data/test/pages-demo/views/demo/config.txt +0 -3
  299. data/test/pages-demo/views/demo/output/panes/footer.html +0 -1
  300. data/test/pages-demo/views/demo/output/panes/header.html +0 -1
  301. data/test/pages-demo/views/demo/output/panes/left.html +0 -1
  302. data/test/pages-demo/views/demo/output/panes/main.html +0 -1
  303. data/test/pages-demo/views/demo/output/panes/right.html +0 -1
  304. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +0 -5
  305. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +0 -4
  306. data/test/scriptorium-TEST-1754622690-146/config/common.js +0 -57
  307. data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +0 -1
  308. data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +0 -9
  309. data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +0 -1
  310. data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +0 -4
  311. data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +0 -3
  312. data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +0 -8
  313. data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +0 -6
  314. data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +0 -1
  315. data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +0 -1
  316. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +0 -1
  317. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +0 -1
  318. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +0 -14
  319. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +0 -13
  320. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +0 -1
  321. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +0 -5
  322. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +0 -4
  323. data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +0 -57
  324. data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +0 -5
  325. data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +0 -2
  326. data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +0 -9
  327. data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +0 -4
  328. data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +0 -5
  329. data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +0 -3
  330. data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +0 -5
  331. data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +0 -3
  332. data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +0 -7
  333. data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +0 -3
  334. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +0 -3
  335. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +0 -3
  336. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +0 -3
  337. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +0 -3
  338. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +0 -3
  339. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +0 -1
  340. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +0 -1
  341. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +0 -1
  342. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +0 -1
  343. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +0 -1
  344. data/ui/web/tmp/web_server.pid +0 -1
  345. /data/{test/pages-demo/views/demo/config/bootstrap_css.txt → lib/scriptorium/support/bootstrap/css.txt} +0 -0
  346. /data/{test/pages-demo/views/demo/config/bootstrap_js.txt → lib/scriptorium/support/bootstrap/js.txt} +0 -0
  347. /data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/reddit.txt +0 -0
  348. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout → lib/scriptorium/support/templates}/layout.txt +0 -0
  349. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt → lib/scriptorium/support/theme/footer.lt3} +0 -0
  350. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt → lib/scriptorium/support/theme/left.lt3} +0 -0
  351. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt → lib/scriptorium/support/theme/main.lt3} +0 -0
  352. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt → lib/scriptorium/support/theme/right.lt3} +0 -0
  353. /data/test/manual/banner-tests/{config.txt → svg.txt} +0 -0
  354. /data/test/manual/{test6.rb → test_advanced_widgets.rb} +0 -0
  355. /data/test/manual/{test1.rb → test_basic_posts.rb} +0 -0
  356. /data/test/manual/{test4.rb → test_layout_widgets.rb} +0 -0
  357. /data/test/manual/{test5.rb → test_pagination.rb} +0 -0
  358. /data/test/manual/{test3.rb → test_random_posts.rb} +0 -0
@@ -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,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
+
@@ -2,6 +2,7 @@
2
2
  # List one platform per line to enable social meta tags
3
3
  facebook
4
4
  twitter
5
+ reddit
5
6
 
6
7
  # Note: No Facebook App ID or Twitter username required for basic meta tags
7
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
@@ -0,0 +1,55 @@
1
+ <style>
2
+ body { font-family: verdana; line-height: 1.1 }
3
+
4
+ .recent-title a {
5
+ color: #010101;
6
+ font-family: verdana;
7
+ font-size: 28px;
8
+ float: right;
9
+ display: inline-block;
10
+ text-align: top;
11
+ text-decoration: none;
12
+ }
13
+
14
+ .recent-title a:hover {
15
+ text-decoration: none;
16
+ }
17
+
18
+ .recent-title-text a {
19
+ color: #0101a1;
20
+ font-family: verdana;
21
+ font-size: 22px;
22
+ display: block;
23
+ text-decoration: none;
24
+ }
25
+
26
+ .recent-title-text a:hover {
27
+ text-decoration: none;
28
+ }
29
+
30
+ .recent-date {
31
+ color: #9a9a9a;
32
+ font-family: verdana;
33
+ font-size: 15px;
34
+ display: block;
35
+ float: left;
36
+ text-align: top;
37
+ }
38
+
39
+ .mydrop {
40
+ color: #444444;
41
+ float: left;
42
+ text-align: top;
43
+ # font-family: Verdana;
44
+ font-size: 38px;
45
+ line-height: 38px;
46
+ # padding-top: 0px;
47
+ padding-right: 8px;
48
+ padding-left: 3px;
49
+ }
50
+
51
+ .thumbnail img {
52
+ max-height: 100%;
53
+ max-width: 100%;
54
+ }
55
+ </style>
@@ -0,0 +1,16 @@
1
+ <tr style="margin-bottom: %{entry.margin.bottom};">
2
+ <td width=%{entry.date.width} valign=top align=right>
3
+ <div style="text-align: %{entry.date.alignment}; font-size: %{entry.date.size}; color: %{entry.date.color}; line-height: %{entry.line.height};">
4
+ <div style="height: %{entry.date.spacing};">&nbsp;</div>
5
+ <div>%{formatted_date}</div>
6
+ </div>
7
+ </td>
8
+ <td valign=top>
9
+ <div style="font-size: %{entry.title.size}; line-height: %{entry.line.height};">
10
+ <div><a href="javascript:void(0)"
11
+ style="text-decoration: none; color: %{entry.title.color};"
12
+ onclick="load_main('index.html?post=%{post.slug}')">%{post.title}</a></div>
13
+ <div style="font-size: %{entry.blurb.size}; color: %{entry.blurb.color};">%{post.blurb}</div>
14
+ </div>
15
+ </td>
16
+ </tr>
@@ -1,12 +1,12 @@
1
1
  . Initial file created by StandardFiles#post_template(num)
2
-
3
- .id %{num}
4
- .created %{created}
2
+ .created
5
3
 
6
4
  .title %{title}
7
- .blurb %{blurb}
5
+ .blurb
6
+ %{blurb}
7
+ .end
8
8
 
9
9
  .views %{views}
10
10
  .tags %{tags}
11
11
 
12
- BEGIN HERE...
12
+ %{body}
@@ -0,0 +1,104 @@
1
+ <!-- theme: standard -->
2
+ <style>
3
+ body { font-family: verdana; line-height: 1.1 }
4
+
5
+ /* Dropcap styles */
6
+ .dropcap {
7
+ float: left;
8
+ font-size: 3em;
9
+ line-height: 0.8;
10
+ margin: 0.1em 0.1em 0 0;
11
+ padding: 0;
12
+ font-weight: bold;
13
+ }
14
+
15
+ /* Pullquote styles */
16
+ .pullquote {
17
+ float: right;
18
+ width: 200px;
19
+ margin: 0 0 20px 20px;
20
+ padding: 15px;
21
+ background: #f5f5f5;
22
+ border-left: 4px solid #333;
23
+ font-size: 1.2em;
24
+ font-style: italic;
25
+ line-height: 1.4;
26
+ color: #666;
27
+ border-radius: 4px;
28
+ }
29
+
30
+ .pullquote-left {
31
+ float: left;
32
+ margin: 0 20px 20px 0;
33
+ }
34
+
35
+ .pullquote-right {
36
+ float: right;
37
+ margin: 0 0 20px 20px;
38
+ }
39
+ </style>
40
+
41
+ <div align='right'><a style="text-decoration: none;" href="../index.html">
42
+ <img src="../assets/icons/ui/back.png" width=32 height=32 alt="Go to Index" style="vertical-align: middle;"></img> Back to index</a>
43
+ </div>
44
+ <div style="display: flex; justify-content: space-between; align-items: baseline;">
45
+ <span style="text-align: left; font-size: 1.5em;">%{post.title}</span>
46
+ <span style="text-align: right; font-size: 0.9em;">%{reddit_button}%{post.date}</span>
47
+ </div>
48
+ <hr>
49
+ %{post.body}
50
+ <hr>
51
+ <div style="text-align: right; font-size: 0.8em;">%{post.tags}</div>
52
+ <div style="text-align: center; margin-top: 20px;">
53
+ <button onclick="copyPermalinkToClipboard(this)" style="padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">Copy link</button>
54
+ </div>
55
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
56
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
57
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/ruby.min.js"></script>
58
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/elixir.min.js"></script>
59
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
60
+ <script>
61
+ if (typeof hljs !== 'undefined') { hljs.highlightAll(); }
62
+
63
+ // If this post is injected into a container (SPA), try to highlight that container only
64
+ try {
65
+ if (typeof hljs !== 'undefined') {
66
+ var container = document.currentScript && document.currentScript.parentElement;
67
+ if (container) {
68
+ container.querySelectorAll('pre code').forEach(function(el){ hljs.highlightElement(el); });
69
+ }
70
+ }
71
+ } catch (e) {}
72
+ </script>
73
+ <script>
74
+ // Fallback clipboard handler for standalone post pages if common JS isn't loaded
75
+ if (typeof copyPermalinkToClipboard === 'undefined') {
76
+ function copyPermalinkToClipboard(button) {
77
+ const currentUrl = window.location.href;
78
+ let permalinkUrl;
79
+ if (currentUrl.includes('?post=')) {
80
+ const postSlug = currentUrl.split('?post=')[1];
81
+ const parts = window.location.pathname.split('/');
82
+ parts.pop();
83
+ const baseUrl = window.location.origin + parts.join('/') + '/';
84
+ permalinkUrl = baseUrl + 'permalink/' + postSlug;
85
+ } else {
86
+ permalinkUrl = currentUrl;
87
+ }
88
+ navigator.clipboard.writeText(permalinkUrl).then(function(){
89
+ const btn = button || document.activeElement;
90
+ if (!btn) { return; }
91
+ const originalText = btn.textContent;
92
+ btn.textContent = 'Copied!';
93
+ btn.style.background = '#28a745';
94
+ setTimeout(function(){
95
+ btn.textContent = originalText;
96
+ btn.style.background = '#007bff';
97
+ }, 2000);
98
+ }).catch(function(err){
99
+ console.error('Failed to copy: ', err);
100
+ alert('Failed to copy link to clipboard');
101
+ });
102
+ }
103
+ }
104
+ </script>
@@ -1,4 +1,4 @@
1
1
  # <!-- Section: header -->
2
2
  # Contents of header
3
3
  # (may include banner, title, navbar, ...)
4
- title
4
+ banner svg