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
@@ -6,7 +6,7 @@
6
6
  body {
7
7
  font-family: Arial, sans-serif;
8
8
  margin: 0;
9
- padding: 20px;
9
+ padding: 10px;
10
10
  background-color: #f5f5f5;
11
11
  }
12
12
  .container {
@@ -20,19 +20,19 @@
20
20
  line-height: 1;
21
21
  }
22
22
  .header p {
23
- margin: 5px 0 0 0;
23
+ margin: 3px 0 0 0;
24
24
  font-size: 16px;
25
25
  opacity: 0.9;
26
26
  }
27
27
  .content {
28
- padding: 20px;
28
+ padding: 10px;
29
29
  }
30
30
  .banner-section {
31
- margin-bottom: 30px;
31
+ margin-bottom: 15px;
32
32
  text-align: center;
33
33
  }
34
34
  .banner-section h2 {
35
- margin-bottom: 15px;
35
+ margin-bottom: 8px;
36
36
  color: #333;
37
37
  }
38
38
  .banner-display {
@@ -40,32 +40,32 @@
40
40
  .actions-grid {
41
41
  display: grid;
42
42
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
43
- gap: 20px;
44
- margin-bottom: 30px;
43
+ gap: 10px;
44
+ margin-bottom: 15px;
45
45
  }
46
46
  .action-card {
47
47
  border: 1px solid #ddd;
48
48
  border-radius: 6px;
49
- padding: 20px;
49
+ padding: 12px;
50
50
  text-align: center;
51
51
  transition: all 0.2s;
52
52
  }
53
53
  .action-card:hover {
54
54
  border-color: #007bff;
55
- box-shadow: 0 2px 8px rgba(0,123,255,0.2);
55
+ box-shadow: 0 1px 4px rgba(0,123,255,0.2);
56
56
  }
57
57
  .action-card h3 {
58
- margin: 0 0 10px 0;
58
+ margin: 0 0 6px 0;
59
59
  color: #333;
60
60
  }
61
61
  .action-card p {
62
- margin: 0 0 15px 0;
62
+ margin: 0 0 8px 0;
63
63
  color: #666;
64
64
  font-size: 14px;
65
65
  }
66
66
  .btn {
67
67
  display: inline-block;
68
- padding: 6px 12px;
68
+ padding: 4px 8px;
69
69
  background: #007bff;
70
70
  color: white;
71
71
  text-decoration: none;
@@ -84,55 +84,696 @@
84
84
  }
85
85
  .view-info {
86
86
  background: #f8f9fa;
87
- padding: 15px;
87
+ padding: 10px;
88
88
  border-radius: 6px;
89
- margin-bottom: 20px;
89
+ margin-bottom: 10px;
90
90
  }
91
91
  .view-info h3 {
92
- margin: 0 0 10px 0;
92
+ margin: 0 0 6px 0;
93
93
  color: #333;
94
94
  }
95
95
  .view-info p {
96
- margin: 5px 0;
96
+ margin: 3px 0;
97
97
  color: #666;
98
98
  }
99
99
  .config-buttons {
100
100
  display: flex;
101
- gap: 10px;
102
- margin-bottom: 20px;
101
+ gap: 6px;
102
+ margin-bottom: 10px;
103
103
  }
104
104
  .button-group {
105
- margin-top: 20px;
105
+ margin-top: 10px;
106
106
  text-align: center;
107
107
  }
108
+
109
+ /* Hover actions styles */
110
+ .actions-container {
111
+ position: relative;
112
+ display: inline-block;
113
+ }
114
+
115
+ .gear-icon {
116
+ font-size: 16px;
117
+ cursor: pointer;
118
+ color: #666;
119
+ display: inline-block;
120
+ height: 20px;
121
+ line-height: 20px;
122
+ vertical-align: middle;
123
+ }
124
+
125
+ .action-icons {
126
+ display: none;
127
+ position: absolute;
128
+ top: 50%;
129
+ right: 100%;
130
+ transform: translateY(-50%);
131
+ background: white;
132
+ border: 1px solid #ddd;
133
+ border-radius: 4px;
134
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
135
+ padding: 4px;
136
+ z-index: 1000;
137
+ flex-direction: column;
138
+ gap: 2px;
139
+ justify-content: center;
140
+ align-items: center;
141
+ min-width: 40px;
142
+ margin-right: 8px;
143
+ }
144
+
145
+ .action-icon {
146
+ font-size: 16px;
147
+ text-decoration: none;
148
+ margin: 0 1px;
149
+ transition: transform 0.2s;
150
+ display: inline-block;
151
+ height: 20px;
152
+ line-height: 20px;
153
+ vertical-align: middle;
154
+ }
155
+
156
+ .action-icon:hover {
157
+ transform: scale(1.2);
158
+ }
159
+
160
+ /* Show action icons on click, hide gear */
161
+ .actions-container.active .gear-icon {
162
+ display: none;
163
+ }
164
+
165
+ .actions-container.active .action-icons {
166
+ display: flex;
167
+ }
168
+
169
+ /* Fixed table column widths to prevent jittering */
170
+ .posts-table {
171
+ table-layout: fixed;
172
+ }
173
+
174
+ .posts-table th:nth-child(1), .posts-table td:nth-child(1) { width: 3%; } /* ID */
175
+ .posts-table th:nth-child(2), .posts-table td:nth-child(2) { width: 10%; } /* Published Date */
176
+ .posts-table th:nth-child(3), .posts-table td:nth-child(3) { width: 36%; } /* Title */
177
+ .posts-table th:nth-child(4), .posts-table td:nth-child(4) { width: 3%; } /* Deploy - very narrow for arrow */
178
+ .posts-table th:nth-child(5), .posts-table td:nth-child(5) { width: 10%; } /* Status */
179
+ .posts-table th:nth-child(6), .posts-table td:nth-child(6) { width: 20%; } /* Other Views */
180
+ .posts-table th:nth-child(7), .posts-table td:nth-child(7) { width: 5%; } /* Actions */
108
181
  </style>
109
182
  </head>
110
183
  <body>
111
184
  <div class="container">
112
185
  <div class="header">
113
- <h1>Dashboard for <%= @current_view.name %></h1>
186
+ <h1 style="margin-left: 0.5em;">Dashboard for <%= @current_view.name %></h1>
114
187
  </div>
115
188
 
116
189
  <div class="content">
117
190
  <div class="banner-section">
118
- <div class="banner-display">
191
+ <div class="banner-display" id="header">
119
192
  <%= @banner_svg %>
120
193
  </div>
121
194
  </div>
122
195
 
123
196
  <div class="config-buttons">
124
- <a href="/banner_config" class="btn">Configure Banner</a>
125
- <a href="/navbar_config" class="btn">Configure Navbar</a>
126
- <a href="/edit_pages" class="btn">Edit Pages</a>
197
+ <a href="#" class="btn" style="background: #17a2b8;" title="Preview locally" onclick="try{ showPreviewPopup(); }catch(e){}; window.open('/preview','_blank'); return false;">Preview</a>
198
+ <form method="post" action="/generate_view" style="display: inline;" onsubmit="showGeneratingPopup()">
199
+ <input type="hidden" name="view_name" value="<%= @current_view.name %>">
200
+ <button type="submit" class="btn" style="background: #007bff;">Generate</button>
201
+ </form>
202
+ <button type="button" class="btn" onclick="showUploadAssetForm()" style="background: #6c757d;">Upload Asset</button>
127
203
  <a href="/asset_management" class="btn">Asset Management</a>
128
- <a href="/" class="btn">Create Post</a>
204
+ <a href="/backup_management" class="btn" style="background: #fd7e14;" title="Backup and restore repository">Backup/Restore</a>
205
+ <a href="/deploy" class="btn" style="background: #28a745;" title="Deploy to server" onclick="showUploadingPopup()">Deploy</a>
206
+ <a href="/browse" class="btn" style="background: #6f42c1;" title="Browse deployed site">Browse</a>
129
207
  <a href="/advanced_config" class="btn btn-secondary">Advanced Config</a>
130
208
  </div>
131
209
 
210
+
211
+ <!-- Upload Asset Modal -->
212
+ <div id="uploadAssetModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000;">
213
+ <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; min-width: 400px;">
214
+ <h3>Upload Asset to Post</h3>
215
+ <form method="post" action="/upload_post_asset" enctype="multipart/form-data">
216
+ <div style="margin-bottom: 15px;">
217
+ <label for="post_id" style="display: block; margin-bottom: 5px; font-weight: bold;">Post ID:</label>
218
+ <select id="post_id" name="post_id" required style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
219
+ <option value="">Select a post...</option>
220
+ <% @posts.each do |post| %>
221
+ <option value="<%= post.id %>">#<%= post.id %> - <%= post.title %></option>
222
+ <% end %>
223
+ </select>
224
+ </div>
225
+ <div style="margin-bottom: 15px;">
226
+ <label for="asset_file" style="display: block; margin-bottom: 5px; font-weight: bold;">File:</label>
227
+ <input type="file" id="asset_file" name="file" required style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
228
+ </div>
229
+ <div style="text-align: right;">
230
+ <button type="button" onclick="hideUploadAssetForm()" style="background: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;">Cancel</button>
231
+ <button type="submit" style="background: #007bff; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">Upload Asset</button>
232
+ </div>
233
+ </form>
234
+ </div>
235
+ </div>
236
+
237
+
238
+ <% if params[:error] %>
239
+ <div class="alert alert-danger" style="background: #f8d7da; color: #721c24; padding: 10px; border-radius: 4px; margin: 10px 0; border: 1px solid #f5c6cb;">
240
+ <%= params[:error] %>
241
+ </div>
242
+ <% end %>
243
+
244
+ <!-- Create Post Button -->
245
+ <div style="margin-bottom: 20px;">
246
+ <button type="button" onclick="toggleCreatePostForm()" id="createPostButton" style="background: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: bold;">Create Post</button>
247
+ </div>
248
+
249
+ <!-- Create Post Section (Hidden by default) -->
250
+ <div id="createPostSection" class="create-post-section" style="display: none; margin-bottom: 20px; padding: 15px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px;">
251
+ <h3 style="margin-top: 0;">Create New Post</h3>
252
+ <form method="post" action="/create_post" id="createPostForm">
253
+ <div style="display: flex; gap: 15px; align-items: end; flex-wrap: wrap;">
254
+ <div style="flex: 1; min-width: 200px;">
255
+ <label for="post_title" style="display: block; margin-bottom: 5px; font-weight: bold;">Title:</label>
256
+ <input type="text" id="post_title" name="title" required style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
257
+ </div>
258
+ <div style="flex: 1; min-width: 200px;">
259
+ <label for="post_blurb" style="display: block; margin-bottom: 5px; font-weight: bold;">Blurb:</label>
260
+ <input type="text" id="post_blurb" name="blurb" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
261
+ </div>
262
+ <div style="flex: 1; min-width: 200px;">
263
+ <label for="post_tags" style="display: block; margin-bottom: 5px; font-weight: bold;">Tags (comma-separated):</label>
264
+ <input type="text" id="post_tags" name="tags" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
265
+ </div>
266
+ </div>
267
+
268
+ <!-- View Selection -->
269
+ <div style="margin-top: 15px;">
270
+ <label style="display: block; margin-bottom: 8px; font-weight: bold;">Views:</label>
271
+ <div style="display: flex; gap: 15px; flex-wrap: wrap;">
272
+ <% @views.each do |view| %>
273
+ <label style="display: flex; align-items: center; gap: 5px; cursor: pointer;">
274
+ <input type="checkbox" name="views[]" value="<%= view.name %>"
275
+ <%= 'checked' if view.name == @current_view.name %>
276
+ style="margin: 0;">
277
+ <span><%= view.name %></span>
278
+ </label>
279
+ <% end %>
280
+ </div>
281
+ </div>
282
+
283
+ <!-- Form Buttons -->
284
+ <div style="margin-top: 15px; text-align: right;">
285
+ <button type="button" onclick="hideCreatePostForm()" style="background: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;">Cancel</button>
286
+ <button type="submit" style="background: #28a745; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">Edit</button>
287
+ </div>
288
+ </form>
289
+ </div>
290
+
291
+ <% if @posts && !@posts.empty? %>
292
+ <div class="posts-section">
293
+ <h3>Posts (<%= @total_posts %> total)</h3>
294
+ <!-- DEBUG: Total posts loaded: <%= @posts.length %>, Deleted posts: <%= @posts.select { |p| p.deleted }.length %> -->
295
+ <div class="posts-table">
296
+ <table class="posts-table" style="width: 90%; border-collapse: collapse; margin: 10px auto 10px 10px;">
297
+ <thead>
298
+ <tr style="background-color: #f2f2f2;">
299
+ <th style="padding: 6px; text-align: left; border: 1px solid #ddd;">ID</th>
300
+ <th style="padding: 6px; text-align: left; border: 1px solid #ddd;">Published</th>
301
+ <th style="padding: 6px; text-align: left; border: 1px solid #ddd; width: 36%;">Title</th>
302
+ <th style="padding: 6px; text-align: center; border: 1px solid #ddd;" title="Deployed? * = no"><svg width="28" height="28" viewBox="0 0 16 16" style="display: inline-block;"><polygon points="8,2 2,10 6,10 6,14 10,14 10,10 14,10" fill="#00aa00" stroke="#008800" stroke-width="0.5"/></svg></th>
303
+ <th style="padding: 6px; text-align: left; border: 1px solid #ddd; width: 10%;">Status</th>
304
+ <th style="padding: 6px; text-align: left; border: 1px solid #ddd; width: 20%;">Other Views</th>
305
+ <th style="padding: 6px; text-align: center; border: 1px solid #ddd; width: 5%;">Actions</th>
306
+ </tr>
307
+ </thead>
308
+ <tbody>
309
+ <% @posts.each do |post| %>
310
+ <tr style="<%= 'opacity: 0.6;' if post.deleted %>">
311
+ <!-- Col 1: ID -->
312
+ <td style="padding: 6px; border: 1px solid #ddd; font-family: monospace; text-align: right;"><%= post.id %></td>
313
+ <!-- Col 2: Published Date -->
314
+ <td style="padding: 6px; border: 1px solid #ddd;"><%= post.pubdate ? post.pubdate.split(' ').first : '—' %></td>
315
+ <!-- Col 3: Title -->
316
+ <td style="padding: 6px; border: 1px solid #ddd;">
317
+ <a href="#" onclick="openPostPreview(<%= post.id %>); return false;" style="color: #007bff; text-decoration: none; cursor: pointer; <%= 'text-decoration: line-through;' if post.deleted %>" title="Preview <%= post.title %>">
318
+ <%= post.title %>
319
+ </a>
320
+ </td>
321
+ <!-- Col 4: Deploy Status -->
322
+ <td style="padding: 6px; border: 1px solid #ddd; text-align: center;">
323
+ <% if post.deleted %>
324
+ <!-- Deleted posts don't show deployment status -->
325
+ <% elsif post.meta["post.deployed"] == "yes" %>
326
+ <!-- Deployed - show nothing -->
327
+ <% else %>
328
+ <!-- Not deployed - show red asterisk -->
329
+ <span style="color: #dc3545; font-weight: bold; font-size: 18px;">*</span>
330
+ <% end %>
331
+ </td>
332
+ <!-- Col 5: Publication Status -->
333
+ <td style="padding: 6px; border: 1px solid #ddd; text-align: left;">
334
+ <% if post.deleted %>
335
+ <span style="color: #dc3545; font-weight: bold;" title="Deleted">deleted</span>
336
+ <% elsif post.meta["post.published"] == "no" %>
337
+ <span style="color: #666;" title="Unpublished">unpublished</span>
338
+ <% elsif post.meta["post.published"] == "yes" %>
339
+ <span style="color: #28a745;" title="Published">published</span>
340
+ <% end %>
341
+ </td>
342
+ <!-- Col 6: Other Views -->
343
+ <td style="padding: 6px; border: 1px solid #ddd;">
344
+ <% other_views = post.views_array.reject { |v| v == @current_view.name } %>
345
+ <%= other_views.join(', ') %>
346
+ </td>
347
+ <!-- Col 7: Actions -->
348
+ <td style="padding: 6px; border: 1px solid #ddd; text-align: center;">
349
+ <div class="actions-container">
350
+ <span class="gear-icon">⚙️</span>
351
+ <div class="action-icons">
352
+ <a href="/edit_post/<%= post.id %>" class="action-icon" title="Edit">✏️</a>
353
+ <% if post.deleted %>
354
+ <form method="post" action="/restore_post/<%= post.id %>" style="display: inline;">
355
+ <button type="submit" class="action-icon" title="Restore" style="background: none; border: none; cursor: pointer; font-size: 16px;">🔄</button>
356
+ </form>
357
+ <% else %>
358
+ <form method="post" action="/delete_post/<%= post.id %>" style="display: inline;">
359
+ <button type="submit" class="action-icon" title="Delete" style="background: none; border: none; cursor: pointer; font-size: 16px;">🗑️</button>
360
+ </form>
361
+ <button type="button" class="action-icon toggle-status-btn" data-post-id="<%= post.id %>" title="<%= post.meta["post.published"] == "no" ? 'Publish' : 'Unpublish' %>" style="background: none; border: none; cursor: pointer; font-size: 14px; padding: 2px 6px; border-radius: 3px; color: #007bff;">
362
+ <% if post.meta["post.published"] == "no" %>
363
+ PUBLISH
364
+ <% else %>
365
+ UNPUBLISH
366
+ <% end %>
367
+ </button>
368
+ <% end %>
369
+ </div>
370
+ </div>
371
+ </td>
372
+ </tr>
373
+ <% end %>
374
+ </tbody>
375
+ </table>
376
+ </div>
377
+
378
+ <% if @total_pages > 1 %>
379
+ <div class="pagination" style="text-align: center; margin: 10px 0;">
380
+ <div style="display: flex; justify-content: center; gap: 3px; flex-wrap: wrap;">
381
+ <% if @current_page > 1 %>
382
+ <a href="/view/<%= @current_view.name %>?page=<%= @current_page - 1 %>" class="btn btn-secondary">&laquo; Previous</a>
383
+ <% end %>
384
+
385
+ <% 1.upto(@total_pages) do |page_num| %>
386
+ <% if page_num == @current_page %>
387
+ <span class="btn" style="background: #0056b3; cursor: default; font-size: 16px;"><%= page_num %></span>
388
+ <% else %>
389
+ <a href="/view/<%= @current_view.name %>?page=<%= page_num %>" class="btn"><%= page_num %></a>
390
+ <% end %>
391
+ <% end %>
392
+
393
+ <% if @current_page < @total_pages %>
394
+ <a href="/view/<%= @current_view.name %>?page=<%= @current_page + 1 %>" class="btn btn-secondary">Next &raquo;</a>
395
+ <% end %>
396
+ </div>
397
+ </div>
398
+ <% end %>
399
+ </div>
400
+ <% else %>
401
+ <div class="posts-section">
402
+ <h3>Posts</h3>
403
+ <p>No posts found in this view.</p>
404
+ </div>
405
+ <% end %>
406
+
132
407
  <div class="button-group">
133
408
  <a href="/" class="btn btn-secondary">Back to Main Dashboard</a>
134
409
  </div>
135
410
  </div>
136
411
  </div>
412
+
413
+ <script>
414
+ // Global functions accessible from HTML onclick attributes
415
+ function ensureOverlay(id, text, bg) {
416
+ var el = document.getElementById(id);
417
+ if (!el) {
418
+ el = document.createElement('div');
419
+ el.id = id;
420
+ el.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 16px 22px; border-radius: 8px; z-index: 9999; color: white; font-weight: bold; box-shadow: 0 4px 12px rgba(0,0,0,0.3);';
421
+ document.body.appendChild(el);
422
+ }
423
+ el.style.background = bg;
424
+ el.textContent = text;
425
+ el.style.display = 'block';
426
+ return el;
427
+ }
428
+ function showPreviewPopup() { try { ensureOverlay('preview-popup', 'Opening preview...', '#17a2b8'); } catch(e) {} }
429
+
430
+ function toggleCreatePostForm() {
431
+ console.log('toggleCreatePostForm called');
432
+ let form = document.getElementById('createPostSection');
433
+ let button = document.getElementById('createPostButton');
434
+ console.log('Form element:', form);
435
+ console.log('Button element:', button);
436
+ console.log('Form display style:', form.style.display);
437
+
438
+ if (form.style.display === 'none' || form.style.display === '') {
439
+ form.style.display = 'block';
440
+ button.textContent = 'Hide';
441
+ console.log('Showing form');
442
+ } else {
443
+ form.style.display = 'none';
444
+ button.textContent = 'Create Post';
445
+ console.log('Hiding form');
446
+ }
447
+ }
448
+
449
+ function hideCreatePostForm() {
450
+ let form = document.getElementById('createPostSection');
451
+ let button = document.getElementById('createPostButton');
452
+ form.style.display = 'none';
453
+ button.textContent = 'Create Post';
454
+ }
455
+
456
+ function openEditModal(postId) {
457
+ // Hide the create post form
458
+ hideCreatePostForm();
459
+
460
+ // Show the edit modal
461
+ let modal = document.getElementById('editPostModal');
462
+ if (modal) {
463
+ modal.style.display = 'block';
464
+ // Load post content via AJAX
465
+ loadPostContent(postId);
466
+ }
467
+ }
468
+
469
+ function closeEditModal() {
470
+ let modal = document.getElementById('editPostModal');
471
+ if (modal) {
472
+ modal.style.display = 'none';
473
+ }
474
+ }
475
+
476
+ function loadPostContent(postId) {
477
+ // Set the post ID in the form
478
+ document.getElementById('editPostId').value = postId;
479
+
480
+ // Set the form action to the correct route
481
+ document.getElementById('editPostForm').action = `/save_post/${postId}`;
482
+
483
+ // Load post content via AJAX
484
+ fetch(`/api/post_content/${postId}`)
485
+ .then(response => response.text())
486
+ .then(content => {
487
+ if (editor) {
488
+ editor.setValue(content);
489
+ } else {
490
+ document.getElementById('editContent').value = content;
491
+ }
492
+ })
493
+ .catch(error => {
494
+ console.error('Error loading post content:', error);
495
+ document.getElementById('editContent').value = 'Error loading post content.';
496
+ });
497
+ }
498
+
499
+ function showUploadAssetForm() {
500
+ document.getElementById('uploadAssetModal').style.display = 'block';
501
+ }
502
+
503
+ function hideUploadAssetForm() {
504
+ document.getElementById('uploadAssetModal').style.display = 'none';
505
+ }
506
+
507
+ function showUploadingPopup() {
508
+ console.log('showUploadingPopup called');
509
+ let popup = document.getElementById('uploading-popup');
510
+ if (!popup) {
511
+ popup = document.createElement('div');
512
+ popup.id = 'uploading-popup';
513
+ popup.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 20px 30px; border-radius: 8px; z-index: 1000; font-size: 16px; font-weight: bold; box-shadow: 0 4px 12px rgba(0,0,0,0.3); background: #17a2b8; color: white;';
514
+ popup.textContent = 'Uploading...';
515
+ document.body.appendChild(popup);
516
+ console.log('Created uploading popup');
517
+ }
518
+ popup.style.display = 'block';
519
+ console.log('Uploading popup should be visible');
520
+ }
521
+
522
+ function hideUploadingPopup() {
523
+ console.log('hideUploadingPopup called');
524
+ let popup = document.getElementById('uploading-popup');
525
+ if (popup) {
526
+ popup.style.display = 'none';
527
+ console.log('Uploading popup hidden');
528
+ } else {
529
+ console.log('No uploading popup found to hide');
530
+ }
531
+ }
532
+
533
+ function openPostPreview(postId) {
534
+ // Get the post data from the table to find the title and construct the slug
535
+ const postRow = document.querySelector(`a[onclick*="openPostPreview(${postId})"]`);
536
+ if (!postRow) {
537
+ console.error('Could not find post row for ID:', postId);
538
+ alert('Error: Could not find post');
539
+ return;
540
+ }
541
+ const postTitle = postRow.textContent.trim();
542
+
543
+ // Construct the expected filename based on the pattern we saw
544
+ const paddedId = postId.toString().padStart(4, '0');
545
+ const slug = postTitle.toLowerCase()
546
+ .replace(/[^a-z0-9\s-]/g, '') // Remove special chars
547
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
548
+ .replace(/-+/g, '-') // Replace multiple hyphens with single
549
+ .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
550
+
551
+ const filename = `${paddedId}-${slug}.html`;
552
+ const currentView = '<%= @current_view.name %>';
553
+ // Open the individual post file directly for the current view
554
+ const url = `/preview/${currentView}/posts/${filename}`;
555
+
556
+ // Open in a new window
557
+ window.open(url, '_blank', 'width=1200,height=800,scrollbars=yes,resizable=yes');
558
+ }
559
+
560
+
561
+ document.addEventListener('DOMContentLoaded', function() {
562
+ // Check if we should open the edit modal
563
+ const urlParams = new URLSearchParams(window.location.search);
564
+ const editPostId = urlParams.get('edit_post');
565
+ if (editPostId) {
566
+ openEditModal(editPostId);
567
+ // Clean up URL to remove edit_post parameter
568
+ const newUrl = window.location.pathname + (urlParams.get('message') ? '?message=' + encodeURIComponent(urlParams.get('message')) : '');
569
+ window.history.replaceState({}, document.title, newUrl);
570
+ }
571
+
572
+ // Handle status toggle buttons
573
+ document.querySelectorAll('.toggle-status-btn').forEach(function(btn) {
574
+ btn.addEventListener('click', function() {
575
+ const postId = this.getAttribute('data-post-id');
576
+ const button = this;
577
+
578
+ // Show loading state
579
+ button.style.opacity = '0.5';
580
+
581
+ fetch('/toggle_post_status/' + postId, {
582
+ method: 'POST',
583
+ headers: {
584
+ 'Content-Type': 'application/json',
585
+ }
586
+ })
587
+ .then(response => response.json())
588
+ .then(data => {
589
+ if (data.success) {
590
+ // Update the button text and title
591
+ if (data.published) {
592
+ button.innerHTML = 'UNPUBLISH';
593
+ button.title = 'Unpublish';
594
+ button.setAttribute('data-published', 'true');
595
+ } else {
596
+ button.innerHTML = 'PUBLISH';
597
+ button.title = 'Publish';
598
+ button.setAttribute('data-published', 'false');
599
+ }
600
+
601
+ // Update the status column
602
+ const statusCell = button.closest('tr').querySelector('td:nth-child(4)');
603
+ console.log('Status cell found:', statusCell);
604
+ console.log('Current status cell content:', statusCell.innerHTML);
605
+
606
+ if (data.published) {
607
+ // Published - show published status
608
+ statusCell.innerHTML = '<span style="color: #28a745;" title="Published">published</span>';
609
+ console.log('Status updated to published');
610
+ } else {
611
+ // Unpublished - show unpublished status
612
+ statusCell.innerHTML = '<span style="color: #666;" title="Unpublished">unpublished</span>';
613
+ console.log('Status updated to unpublished');
614
+ }
615
+
616
+ // Also update the button's data attribute for future toggles
617
+ button.setAttribute('data-published', data.published.toString());
618
+
619
+ // Show success message temporarily
620
+ showMessage(data.message, 'success');
621
+ } else {
622
+ showMessage('Failed to toggle status', 'error');
623
+ }
624
+ })
625
+ .catch(error => {
626
+ console.error('Error:', error);
627
+ showMessage('Failed to toggle status', 'error');
628
+ })
629
+ .finally(() => {
630
+ button.style.opacity = '1';
631
+ });
632
+ });
633
+ });
634
+
635
+ function showMessage(message, type) {
636
+ // Create or update message element
637
+ let messageEl = document.getElementById('status-message');
638
+ if (!messageEl) {
639
+ messageEl = document.createElement('div');
640
+ messageEl.id = 'status-message';
641
+ messageEl.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 15px 25px; border-radius: 8px; z-index: 1000; font-size: 16px; font-weight: bold; box-shadow: 0 4px 12px rgba(0,0,0,0.3);';
642
+ document.body.appendChild(messageEl);
643
+ }
644
+
645
+ messageEl.textContent = message;
646
+ messageEl.style.background = type === 'success' ? '#28a745' : '#dc3545';
647
+ messageEl.style.color = 'white';
648
+
649
+ // Hide after 3 seconds
650
+ setTimeout(() => {
651
+ messageEl.style.display = 'none';
652
+ }, 3000);
653
+ }
654
+
655
+
656
+
657
+ // Check for deployment success message in URL parameters
658
+ const deployParams = new URLSearchParams(window.location.search);
659
+ const deployMessage = deployParams.get('deploy_success');
660
+ const hideUploading = deployParams.get('hide_uploading');
661
+
662
+ if (deployMessage) {
663
+ showMessage(deployMessage, 'success');
664
+ // Clean up URL
665
+ const newUrl = window.location.pathname;
666
+ window.history.replaceState({}, document.title, newUrl);
667
+ }
668
+
669
+ // Clean up any message parameters
670
+ const message = urlParams.get('message');
671
+ if (message) {
672
+ showMessage(message, 'success');
673
+ // Clean up URL after showing message
674
+ setTimeout(() => {
675
+ const newUrl = window.location.pathname;
676
+ window.history.replaceState({}, document.title, newUrl);
677
+ }, 1000);
678
+ }
679
+
680
+ // Hide uploading popup if deployment completed
681
+ if (hideUploading) {
682
+ hideUploadingPopup();
683
+ }
684
+
685
+ // Add click handlers for action containers
686
+ document.querySelectorAll('.actions-container').forEach(container => {
687
+ container.addEventListener('click', function(e) {
688
+ e.stopPropagation();
689
+ // Close all other action containers
690
+ document.querySelectorAll('.actions-container.active').forEach(active => {
691
+ if (active !== this) {
692
+ active.classList.remove('active');
693
+ }
694
+ });
695
+ // Toggle this container
696
+ this.classList.toggle('active');
697
+ });
698
+ });
699
+
700
+ // Close action containers when clicking elsewhere
701
+ document.addEventListener('click', function() {
702
+ document.querySelectorAll('.actions-container.active').forEach(container => {
703
+ container.classList.remove('active');
704
+ });
705
+ });
706
+ });
707
+ </script>
708
+
709
+ <!-- Edit Post Modal -->
710
+ <div id="editPostModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000;">
711
+ <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border-radius: 8px; width: 80vw; height: 80vh; max-width: 1200px; max-height: 800px; display: flex; flex-direction: column;">
712
+ <!-- Modal Header -->
713
+ <div style="padding: 20px; border-bottom: 1px solid #ddd;">
714
+ <h2 style="margin: 0;">Edit Post</h2>
715
+ </div>
716
+
717
+ <!-- Modal Content -->
718
+ <div style="flex: 1; padding: 20px; overflow: hidden;">
719
+ <form id="editPostForm" method="post" onsubmit="syncCodeMirrorContent()">
720
+ <input type="hidden" id="editPostId" name="post_id" value="">
721
+ <div style="height: 100%; display: flex; flex-direction: column;">
722
+ <label for="editContent" style="margin-bottom: 10px; font-weight: bold;">Post Content:</label>
723
+ <textarea name="content" id="editContent" placeholder="Enter your post content here..." style="flex: 1; font-family: monospace; font-size: 14px; border: 1px solid #ddd; border-radius: 4px; padding: 10px; resize: none;"></textarea>
724
+ </div>
725
+ </form>
726
+ </div>
727
+
728
+ <!-- Modal Footer -->
729
+ <div style="padding: 20px; border-top: 1px solid #ddd; text-align: right;">
730
+ <button type="button" onclick="closeEditModal()" style="background: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;">Cancel</button>
731
+ <button type="submit" form="editPostForm" style="background: #28a745; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">Save Post</button>
732
+ </div>
733
+ </div>
734
+ </div>
735
+
736
+ <!-- CodeMirror CSS and JS -->
737
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
738
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/monokai.min.css">
739
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
740
+
741
+ <script>
742
+ let editor = null;
743
+
744
+ function syncCodeMirrorContent() {
745
+ if (editor) {
746
+ document.getElementById('editContent').value = editor.getValue();
747
+ }
748
+ }
749
+
750
+
751
+ // Initialize CodeMirror when modal opens
752
+ function initCodeMirror() {
753
+ if (!editor) {
754
+ editor = CodeMirror.fromTextArea(document.getElementById('editContent'), {
755
+ mode: 'livetext',
756
+ theme: 'monokai',
757
+ lineNumbers: true,
758
+ lineWrapping: false,
759
+ indentUnit: 2,
760
+ tabSize: 2,
761
+ indentWithTabs: false,
762
+ scrollbarStyle: 'native',
763
+ viewportMargin: Infinity
764
+ });
765
+
766
+ // Hide the original textarea
767
+ document.getElementById('editContent').style.display = 'none';
768
+ }
769
+ }
770
+
771
+ // Override openEditModal to initialize CodeMirror
772
+ const originalOpenEditModal = openEditModal;
773
+ openEditModal = function(postId) {
774
+ originalOpenEditModal(postId);
775
+ setTimeout(initCodeMirror, 100); // Small delay to ensure modal is visible
776
+ };
777
+ </script>
137
778
  </body>
138
779
  </html>