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,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
+ });
@@ -1,43 +1,43 @@
1
1
  module ErrorHelpers
2
2
  # Error message mapping for common errors
3
3
  ERROR_MESSAGES = {
4
- 'CannotReadFileNotFound' => {
4
+ 'ReadFileNotFound' => {
5
5
  message: 'File not found',
6
6
  suggestion: 'The file may have been moved or deleted. Try refreshing the page or recreating the content.'
7
7
  },
8
- 'CannotCreatePostRepoNil' => {
8
+ 'PostRepoNil' => {
9
9
  message: 'Repository not initialized',
10
10
  suggestion: 'Please create or open a repository first.'
11
11
  },
12
- 'CannotCreatePostNumNil' => {
12
+ 'PostNumNil' => {
13
13
  message: 'Invalid post ID',
14
14
  suggestion: 'Please provide a valid post number.'
15
15
  },
16
- 'CannotCreatePostNumEmpty' => {
16
+ 'PostNumEmpty' => {
17
17
  message: 'Post ID is empty',
18
18
  suggestion: 'Please provide a valid post number.'
19
19
  },
20
- 'CannotCreatePostNumInvalid' => {
20
+ 'PostNumInvalid' => {
21
21
  message: 'Invalid post ID format',
22
22
  suggestion: 'Post ID must be a number.'
23
23
  },
24
- 'CannotGetPostIdNil' => {
24
+ 'GetPostIdNil' => {
25
25
  message: 'Post ID not provided',
26
26
  suggestion: 'Please specify a post ID.'
27
27
  },
28
- 'CannotGetPostIdEmpty' => {
28
+ 'GetPostIdEmpty' => {
29
29
  message: 'Post ID is empty',
30
30
  suggestion: 'Please provide a valid post ID.'
31
31
  },
32
- 'CannotGetPostIdInvalid' => {
32
+ 'GetPostIdInvalid' => {
33
33
  message: 'Invalid post ID format',
34
34
  suggestion: 'Post ID must be a number.'
35
35
  },
36
- 'CannotEditFilePathNil' => {
36
+ 'EditFilePathNil' => {
37
37
  message: 'File path not specified',
38
38
  suggestion: 'Please provide a valid file path.'
39
39
  },
40
- 'CannotEditFilePathEmpty' => {
40
+ 'EditFilePathEmpty' => {
41
41
  message: 'File path is empty',
42
42
  suggestion: 'Please provide a valid file path.'
43
43
  },
@@ -45,27 +45,27 @@ module ErrorHelpers
45
45
  message: 'Repository already exists',
46
46
  suggestion: 'The repository directory already exists. You can open it instead.'
47
47
  },
48
- 'CannotLookupViewTargetNil' => {
48
+ 'LookupViewTargetNil' => {
49
49
  message: 'View not specified',
50
50
  suggestion: 'Please provide a valid view name.'
51
51
  },
52
- 'CannotCreateViewNameNil' => {
52
+ 'ViewNameNil' => {
53
53
  message: 'View name not provided',
54
54
  suggestion: 'Please provide a view name.'
55
55
  },
56
- 'CannotCreateViewNameEmpty' => {
56
+ 'ViewNameEmpty' => {
57
57
  message: 'View name is empty',
58
58
  suggestion: 'Please provide a view name.'
59
59
  },
60
- 'CannotCreateViewTitleNil' => {
60
+ 'ViewTitleNil' => {
61
61
  message: 'View title not provided',
62
62
  suggestion: 'Please provide a view title.'
63
63
  },
64
- 'CannotCreateViewTitleEmpty' => {
64
+ 'ViewTitleEmpty' => {
65
65
  message: 'View title is empty',
66
66
  suggestion: 'Please provide a view title.'
67
67
  },
68
- 'CannotCreateViewNameInvalid' => {
68
+ 'ViewNameInvalid' => {
69
69
  message: 'Invalid view name',
70
70
  suggestion: 'View names can only contain letters, numbers, hyphens (-), and underscores (_).'
71
71
  }
@@ -141,13 +141,13 @@
141
141
  <% if @configs[:header] %>
142
142
  <tr>
143
143
  <td><span class="status-indicator <%= @configs[:banner] ? 'status-active' : 'status-inactive' %>"></span></td>
144
- <td><a href="/banner_config" class="btn">Update Banner</a></td>
144
+ <td><a href="/banner_config" class="btn">Configure Banner</a></td>
145
145
  <td>Customize the SVG banner with colors, fonts, and layout options.</td>
146
146
  </tr>
147
147
 
148
148
  <tr>
149
149
  <td><span class="status-indicator <%= @configs[:navbar] ? 'status-active' : 'status-inactive' %>"></span></td>
150
- <td><a href="/navbar_config" class="btn">Update Navbar</a></td>
150
+ <td><a href="/navbar_config" class="btn">Configure Navbar</a></td>
151
151
  <td>Build the navigation menu structure with pages and sections.</td>
152
152
  </tr>
153
153
  <% end %>
@@ -174,6 +174,12 @@
174
174
  <td>Create and edit content pages for this view.</td>
175
175
  </tr>
176
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
+
177
183
  <tr>
178
184
  <td><span class="status-indicator status-active"></span></td>
179
185
  <td><a href="/deploy_config" class="btn">Configure Deployment</a></td>
@@ -476,6 +476,62 @@
476
476
  </div>
477
477
  </div>
478
478
  </div>
479
+
480
+ <!-- Post Assets Column -->
481
+ <div class="assets-column">
482
+ <div class="section">
483
+ <div class="section-header">Post-Specific Assets</div>
484
+ <div class="section-content">
485
+ <% if @post_assets.empty? %>
486
+ <div class="empty-message">No post-specific assets found.</div>
487
+ <% else %>
488
+ <div class="assets-list">
489
+ <table class="assets-table">
490
+ <thead>
491
+ <tr>
492
+ <th style="width: 60px;">Icon</th>
493
+ <th>File Info</th>
494
+ <th>Post</th>
495
+ </tr>
496
+ </thead>
497
+ <tbody>
498
+ <% @post_assets.each do |asset| %>
499
+ <tr>
500
+ <td>
501
+ <div class="asset-icon">
502
+ <% if asset[:filename].match?(/\.(jpg|jpeg|png|gif|svg)$/i) %>
503
+ <img src="/posts/<%= asset[:post_id].to_s.rjust(4, '0') %>/assets/<%= asset[:filename] %>" alt="<%= asset[:filename] %>">
504
+ <% else %>
505
+ 📄
506
+ <% end %>
507
+ </div>
508
+ </td>
509
+ <td>
510
+ <div class="file-info">
511
+ <div class="filename"><%= asset[:filename] %></div>
512
+ <div class="file-details">
513
+ <%= number_to_human_size(asset[:size]) %>
514
+ <% if asset[:dimensions] %>
515
+ • <%= asset[:dimensions] %>
516
+ <% end %>
517
+ </div>
518
+ </div>
519
+ </td>
520
+ <td>
521
+ <div class="post-info">
522
+ <div class="post-title">#<%= asset[:post_id] %></div>
523
+ <div class="post-name"><%= asset[:post_title] %></div>
524
+ </div>
525
+ </td>
526
+ </tr>
527
+ <% end %>
528
+ </tbody>
529
+ </table>
530
+ </div>
531
+ <% end %>
532
+ </div>
533
+ </div>
534
+ </div>
479
535
  </div>
480
536
 
481
537
  <div class="button-group">
@@ -0,0 +1,238 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Backup Management - 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: 1200px;
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: #fd7e14;
22
+ color: white;
23
+ padding: 20px;
24
+ }
25
+ .header h1 {
26
+ margin: 0;
27
+ font-size: 24px;
28
+ }
29
+ .content {
30
+ padding: 20px;
31
+ }
32
+ .message {
33
+ padding: 10px;
34
+ border-radius: 4px;
35
+ margin-bottom: 20px;
36
+ }
37
+ .message.success {
38
+ background: #d4edda;
39
+ color: #155724;
40
+ border: 1px solid #c3e6cb;
41
+ }
42
+ .message.error {
43
+ background: #f8d7da;
44
+ color: #721c24;
45
+ border: 1px solid #f5c6cb;
46
+ }
47
+ .backup-form {
48
+ background: #f8f9fa;
49
+ padding: 20px;
50
+ border-radius: 6px;
51
+ margin-bottom: 30px;
52
+ border: 1px solid #dee2e6;
53
+ }
54
+ .backup-form h3 {
55
+ margin-top: 0;
56
+ color: #495057;
57
+ }
58
+ .form-group {
59
+ margin-bottom: 15px;
60
+ }
61
+ .form-group label {
62
+ display: block;
63
+ margin-bottom: 5px;
64
+ font-weight: bold;
65
+ color: #495057;
66
+ }
67
+ .form-group select,
68
+ .form-group input[type="text"] {
69
+ width: 100%;
70
+ padding: 8px 12px;
71
+ border: 1px solid #ced4da;
72
+ border-radius: 4px;
73
+ font-size: 14px;
74
+ box-sizing: border-box;
75
+ }
76
+ .form-group select:focus,
77
+ .form-group input[type="text"]:focus {
78
+ outline: none;
79
+ border-color: #fd7e14;
80
+ box-shadow: 0 0 0 2px rgba(253, 126, 20, 0.25);
81
+ }
82
+ .btn {
83
+ background: #fd7e14;
84
+ color: white;
85
+ border: none;
86
+ padding: 10px 20px;
87
+ border-radius: 4px;
88
+ cursor: pointer;
89
+ font-size: 14px;
90
+ text-decoration: none;
91
+ display: inline-block;
92
+ margin-right: 10px;
93
+ }
94
+ .btn:hover {
95
+ background: #e55a00;
96
+ }
97
+ .btn-secondary {
98
+ background: #6c757d;
99
+ }
100
+ .btn-secondary:hover {
101
+ background: #545b62;
102
+ }
103
+ .backups-table {
104
+ width: 100%;
105
+ border-collapse: collapse;
106
+ margin-top: 20px;
107
+ }
108
+ .backups-table th,
109
+ .backups-table td {
110
+ padding: 12px;
111
+ text-align: left;
112
+ border-bottom: 1px solid #dee2e6;
113
+ }
114
+ .backups-table th {
115
+ background-color: #f8f9fa;
116
+ font-weight: bold;
117
+ color: #495057;
118
+ }
119
+ .backups-table tr:hover {
120
+ background-color: #f8f9fa;
121
+ }
122
+ .backup-type {
123
+ padding: 4px 8px;
124
+ border-radius: 12px;
125
+ font-size: 12px;
126
+ font-weight: bold;
127
+ text-transform: uppercase;
128
+ }
129
+ .backup-type.full {
130
+ background: #d1ecf1;
131
+ color: #0c5460;
132
+ }
133
+ .backup-type.incr {
134
+ background: #d4edda;
135
+ color: #155724;
136
+ }
137
+ .backup-age {
138
+ color: #6c757d;
139
+ font-size: 14px;
140
+ }
141
+ .no-backups {
142
+ text-align: center;
143
+ color: #6c757d;
144
+ font-style: italic;
145
+ padding: 40px;
146
+ }
147
+ .back-link {
148
+ color: #fd7e14;
149
+ text-decoration: none;
150
+ font-weight: bold;
151
+ }
152
+ .back-link:hover {
153
+ text-decoration: underline;
154
+ }
155
+ </style>
156
+ </head>
157
+ <body>
158
+ <div class="container">
159
+ <div class="header">
160
+ <h1>Backup Management</h1>
161
+ </div>
162
+
163
+ <div class="content">
164
+ <a href="/view/<%= @current_view.name %>" class="back-link">← Back to Dashboard</a>
165
+
166
+ <% if @message %>
167
+ <div class="message success">
168
+ <%= @message %>
169
+ </div>
170
+ <% end %>
171
+
172
+ <% if @error %>
173
+ <div class="message error">
174
+ <%= @error %>
175
+ </div>
176
+ <% end %>
177
+
178
+ <!-- Create Backup Form -->
179
+ <div class="backup-form">
180
+ <h3>Create New Backup</h3>
181
+ <form method="post" action="/backup_management/create">
182
+ <div class="form-group">
183
+ <label for="type">Backup Type:</label>
184
+ <select id="type" name="type" required>
185
+ <option value="">Select backup type...</option>
186
+ <option value="full">Full Backup (Complete repository snapshot)</option>
187
+ <option value="incr">Incremental Backup (Only changed files since last backup)</option>
188
+ </select>
189
+ </div>
190
+
191
+ <div class="form-group">
192
+ <label for="description">Description (optional):</label>
193
+ <input type="text" id="description" name="description" placeholder="e.g., Before major changes, Added new posts, etc.">
194
+ </div>
195
+
196
+ <button type="submit" class="btn">Create Backup</button>
197
+ </form>
198
+ </div>
199
+
200
+ <!-- Backups List -->
201
+ <div>
202
+ <h3>Available Backups</h3>
203
+ <% if @backups && !@backups.empty? %>
204
+ <table class="backups-table">
205
+ <thead>
206
+ <tr>
207
+ <th>Timestamp</th>
208
+ <th>Type</th>
209
+ <th>Description</th>
210
+ <th>Age</th>
211
+ </tr>
212
+ </thead>
213
+ <tbody>
214
+ <% @backups.each do |backup| %>
215
+ <tr>
216
+ <td><%= backup[:timestamp] %></td>
217
+ <td>
218
+ <span class="backup-type <%= backup[:type] %>">
219
+ <%= backup[:type] %>
220
+ </span>
221
+ </td>
222
+ <td><%= backup[:description] || '<em>No description</em>' %></td>
223
+ <td class="backup-age"><%= backup[:age] %></td>
224
+ </tr>
225
+ <% end %>
226
+ </tbody>
227
+ </table>
228
+ <% else %>
229
+ <div class="no-backups">
230
+ <p>No backups available yet.</p>
231
+ <p>Create your first backup using the form above.</p>
232
+ </div>
233
+ <% end %>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </body>
238
+ </html>