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.
- checksums.yaml +4 -4
- data/assets/icons/social/reddit.png +0 -0
- data/assets/icons/social/x-logo.png +0 -0
- data/assets/imagenotfound.jpg +0 -0
- data/bin/sblog +84 -5
- data/bin/scriptorium +1 -0
- data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +0 -1
- data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +0 -29
- data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +0 -19
- data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +1 -1
- data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +1 -1
- data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +1 -1
- data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +0 -10
- data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +1 -4
- data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
- data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
- data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
- data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
- data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
- data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
- data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
- data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
- data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
- data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
- data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
- data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
- data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
- data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
- data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
- data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
- data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
- data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
- data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
- data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
- data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
- data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
- data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
- data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
- data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
- data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
- data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
- data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
- data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
- data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
- data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
- data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
- data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
- data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
- data/doc/imported/0018-ruby-puns/post.html +46 -0
- data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
- data/doc/imported/0020-fffff/metadata.txt +7 -0
- data/doc/imported/0020-fffff/post.html +24 -0
- data/doc/imported/0020-fffff/source.lt3 +12 -0
- data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
- data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
- data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
- data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
- data/doc/imported/0023-doctor-sleep/post.html +63 -0
- data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
- data/doc/imported/0024-just-a-test/metadata.txt +7 -0
- data/doc/imported/0024-just-a-test/post.html +24 -0
- data/doc/imported/0024-just-a-test/source.lt3 +12 -0
- data/doc/imported/import_summary.txt +98 -0
- data/doc/livetext-informal-spec.txt +65 -0
- data/doc/myuserdoc/ch-0.lt3 +31 -0
- data/doc/myuserdoc/ch-1.lt3 +37 -0
- data/doc/myuserdoc/ch-10.lt3 +22 -0
- data/doc/myuserdoc/ch-2.lt3 +37 -0
- data/doc/myuserdoc/ch-3.lt3 +19 -0
- data/doc/myuserdoc/ch-4.lt3 +43 -0
- data/doc/myuserdoc/ch-5.lt3 +22 -0
- data/doc/myuserdoc/ch-6.lt3 +19 -0
- data/doc/myuserdoc/ch-7.lt3 +16 -0
- data/doc/myuserdoc/ch-8.lt3 +13 -0
- data/doc/myuserdoc/ch-9.lt3 +19 -0
- data/doc/myuserdoc/tweak.rb +18 -0
- data/doc/{userdoc-toc.txt → myuserdoc/userdoc-toc.txt} +27 -27
- data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
- data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
- data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
- data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
- data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
- data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
- data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
- data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
- data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
- data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
- data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
- data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
- data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
- data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
- data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
- data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
- data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
- data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
- data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
- data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
- data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
- data/doc/old-posts/0024-just-a-test.lt3 +12 -0
- data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
- data/doc/old-repo +1 -0
- data/doc/reddit_integration.md +2 -2
- data/doc/user.lt3 +0 -3
- data/lib/scriptorium/api.rb +1811 -78
- data/lib/scriptorium/banner_svg.rb +55 -68
- data/lib/scriptorium/contract.rb +3 -2
- data/lib/scriptorium/exceptions.rb +133 -102
- data/lib/scriptorium/helpers.rb +282 -82
- data/lib/scriptorium/post.rb +81 -17
- data/lib/scriptorium/reddit.rb +1 -1
- data/lib/scriptorium/repo.rb +478 -164
- data/lib/scriptorium/standard_files.rb +30 -396
- data/lib/scriptorium/support/common_js/clipboard.js +35 -0
- data/lib/scriptorium/support/common_js/content-loader.js +187 -0
- data/lib/scriptorium/support/common_js/navigation.js +52 -0
- data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
- data/lib/scriptorium/support/config/reddit_template.txt +17 -0
- data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/social.txt +1 -0
- data/lib/scriptorium/support/highlight/css.txt +2 -0
- data/lib/scriptorium/support/highlight/custom.css +119 -0
- data/lib/scriptorium/support/highlight/js.txt +1 -0
- data/lib/scriptorium/support/post_index/config.txt +15 -0
- data/lib/scriptorium/support/post_index/style.css +55 -0
- data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
- data/{test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 → lib/scriptorium/support/templates/initial_post.lt3} +5 -5
- data/lib/scriptorium/support/templates/post.lt3 +104 -0
- data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt → lib/scriptorium/support/theme/header.lt3} +1 -1
- data/lib/scriptorium/theme.rb +83 -70
- data/lib/scriptorium/version.rb +2 -2
- data/lib/scriptorium/view.rb +194 -149
- data/lib/scriptorium.rb +24 -1
- data/lib/skeleton.rb +4 -1
- data/scriptorium.gemspec +2 -1
- data/test/WEB_INTEGRATION_README.md +196 -0
- data/test/all +40 -0
- data/test/banner_svg/unit.rb +267 -35
- data/test/config/deployment.txt +5 -0
- data/test/integration/integration_test.rb +7 -7
- data/test/integration/preview_flow_test.rb +94 -0
- data/test/livetext_plugin_test.rb +453 -182
- data/test/manual/banner-tests/test01.html +82 -18
- data/test/manual/banner-tests/test02.html +82 -18
- data/test/manual/banner-tests/test03.html +82 -18
- data/test/manual/banner-tests/test04.html +89 -25
- data/test/manual/banner-tests/test05.html +89 -25
- data/test/manual/banner-tests/test06.html +89 -25
- data/test/manual/banner-tests/test07.html +89 -25
- data/test/manual/banner-tests/test08.html +82 -18
- data/test/manual/banner-tests/test09.html +82 -18
- data/test/manual/banner-tests/test10.html +82 -18
- data/test/manual/banner-tests/test11.html +82 -18
- data/test/manual/banner-tests/test12.html +82 -18
- data/test/manual/banner-tests/test13.html +82 -18
- data/test/manual/banner-tests/test14.html +82 -18
- data/test/manual/banner-tests/test15.html +82 -18
- data/test/manual/banner-tests/test16.html +82 -18
- data/test/manual/banner-tests/test17.html +82 -18
- data/test/manual/banner-tests/test18.html +90 -26
- data/test/manual/banner-tests/test19.html +90 -26
- data/test/manual/banner-tests/test20.html +90 -26
- data/test/manual/banner-tests/test21.html +90 -26
- data/test/manual/banner-tests/test22.html +90 -26
- data/test/manual/banner-tests/test23.html +90 -26
- data/test/manual/banner-tests/test24.html +90 -26
- data/test/manual/banner-tests/test25.html +89 -25
- data/test/manual/banner_environment.rb +15 -2
- data/test/manual/codemirror_demo.html +773 -0
- data/test/manual/create_posts_for_web.rb +114 -0
- data/test/manual/preview_manual_test.rb +129 -0
- data/test/manual/test_banner_features.rb +14 -14
- data/test/manual/test_banner_integration.rb +115 -0
- data/test/manual/test_banner_radial.rb +87 -0
- data/test/manual/test_syntax_highlighting.rb +60 -40
- data/test/support/preview_utils.rb +88 -0
- data/test/test_gem_assets.rb +48 -0
- data/test/test_helpers.rb +10 -0
- data/test/tui_editor_integration_test.rb +15 -15
- data/test/tui_integration_test.rb +687 -441
- data/test/unit/api.rb +757 -37
- data/test/unit/asset_management.rb +195 -221
- data/test/unit/backup_test.rb +451 -0
- data/test/unit/contract_test.rb +1 -23
- data/test/unit/core.rb +415 -61
- data/test/unit/deploy_config_test.rb +248 -0
- data/test/unit/deploy_test.rb +312 -21
- data/test/unit/edit_post_test.rb +168 -0
- data/test/unit/gem_asset_management.rb +36 -42
- data/test/unit/livetext_basic.rb +23 -35
- data/test/unit/livetext_compatibility.rb +7 -14
- data/test/unit/parse_cmd_test.rb +260 -0
- data/test/unit/{symlink_test.rb → permalink_copy_test.rb} +47 -49
- data/test/unit/post.rb +91 -26
- data/test/unit/post_index_config_test.rb +258 -0
- data/test/unit/post_state_helpers_test.rb +137 -0
- data/test/unit/read_commented_file_test.rb +8 -6
- data/test/unit/repo.rb +75 -54
- data/test/unit/social_test.rb +41 -44
- data/test/unit/syntax_highlighting.rb +70 -0
- data/test/unit/theme_management_test.rb +91 -0
- data/test/unit/view.rb +79 -12
- data/test/unit/widgets.rb +8 -8
- data/test/web_integration_test.rb +231 -0
- data/test/web_test_helper.rb +218 -0
- data/test/web_workflow_test.rb +527 -0
- data/ui/tui/bin/scriptorium +885 -415
- data/ui/web/app/app.rb +1398 -176
- data/ui/web/app/assets/livetext_mode.js +244 -0
- data/ui/web/app/error_helpers.rb +16 -16
- data/ui/web/app/views/advanced_config.erb +8 -2
- data/ui/web/app/views/asset_management.erb +56 -0
- data/ui/web/app/views/backup_management.erb +238 -0
- data/ui/web/app/views/config_widget.erb +232 -0
- data/ui/web/app/views/dashboard.erb +64 -72
- data/ui/web/app/views/deploy_config.erb +3 -0
- data/ui/web/app/views/edit_pages.erb +170 -2
- data/ui/web/app/views/edit_post.erb +130 -9
- data/ui/web/app/views/edit_theme.erb +73 -0
- data/ui/web/app/views/edit_theme_file.erb +74 -0
- data/ui/web/app/views/theme_management.erb +130 -0
- data/ui/web/app/views/view_dashboard.erb +666 -25
- data/ui/web/app/views/widgets.erb +249 -0
- data/ui/web/bin/scriptorium-web +35 -24
- data/ui/web/tmp/timing.log +17 -0
- data/ui/web/tmp/web_server.log +0 -5
- metadata +190 -116
- data/assets/back-icon.png +0 -0
- data/assets/icons/facebook.svg +0 -1
- data/assets/icons/github.svg +0 -1
- data/assets/icons/instagram.svg +0 -1
- data/assets/icons/reddit.svg +0 -1
- data/assets/icons/x.svg +0 -1
- data/assets/icons/youtube.svg +0 -1
- data/bin/scriptorium +0 -1511
- data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +0 -34
- data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +0 -50
- data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +0 -73
- data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +0 -70
- data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +0 -110
- data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +0 -73
- data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +0 -124
- data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +0 -46
- data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +0 -97
- data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +0 -211
- data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +0 -141
- data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +0 -211
- data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +0 -134
- data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +0 -41
- data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +0 -49
- data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +0 -46
- data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +0 -41
- data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +0 -41
- data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +0 -256
- data/lib/scriptorium/syntax_highlighter.rb +0 -234
- data/test/manual/deploy_symlink_demo.rb +0 -142
- data/test/manual/symlink_demo.rb +0 -117
- data/test/manual/test2.rb +0 -12
- data/test/manual/test_banner_from_file.rb +0 -150
- data/test/manual/test_banner_in_header.rb +0 -35
- data/test/manual/test_code_highlighting.rb +0 -68
- data/test/manual/test_complex_header.rb +0 -74
- data/test/manual/test_empty_header.rb +0 -32
- data/test/manual/test_radial_custom.rb +0 -58
- data/test/manual/test_radial_large_radius.rb +0 -52
- data/test/manual/test_svg_debug.rb +0 -47
- data/test/pages-demo/config/currentview.txt +0 -1
- data/test/pages-demo/views/demo/config/common.js +0 -57
- data/test/pages-demo/views/demo/config/footer.txt +0 -1
- data/test/pages-demo/views/demo/config/global-head.txt +0 -8
- data/test/pages-demo/views/demo/config/header.txt +0 -1
- data/test/pages-demo/views/demo/config/layout.txt +0 -1
- data/test/pages-demo/views/demo/config/left.txt +0 -1
- data/test/pages-demo/views/demo/config/main.txt +0 -1
- data/test/pages-demo/views/demo/config/right.txt +0 -1
- data/test/pages-demo/views/demo/config.txt +0 -3
- data/test/pages-demo/views/demo/output/panes/footer.html +0 -1
- data/test/pages-demo/views/demo/output/panes/header.html +0 -1
- data/test/pages-demo/views/demo/output/panes/left.html +0 -1
- data/test/pages-demo/views/demo/output/panes/main.html +0 -1
- data/test/pages-demo/views/demo/output/panes/right.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +0 -4
- data/test/scriptorium-TEST-1754622690-146/config/common.js +0 -57
- data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +0 -1
- data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +0 -9
- data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +0 -1
- data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +0 -4
- data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +0 -3
- data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +0 -8
- data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +0 -6
- data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +0 -1
- data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +0 -1
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +0 -1
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +0 -1
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +0 -14
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +0 -13
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +0 -4
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +0 -57
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +0 -2
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +0 -9
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +0 -4
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +0 -7
- data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +0 -1
- data/ui/web/tmp/web_server.pid +0 -1
- /data/{test/pages-demo/views/demo/config/bootstrap_css.txt → lib/scriptorium/support/bootstrap/css.txt} +0 -0
- /data/{test/pages-demo/views/demo/config/bootstrap_js.txt → lib/scriptorium/support/bootstrap/js.txt} +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/reddit.txt +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout → lib/scriptorium/support/templates}/layout.txt +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt → lib/scriptorium/support/theme/footer.lt3} +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt → lib/scriptorium/support/theme/left.lt3} +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt → lib/scriptorium/support/theme/main.lt3} +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt → lib/scriptorium/support/theme/right.lt3} +0 -0
- /data/test/manual/banner-tests/{config.txt → svg.txt} +0 -0
- /data/test/manual/{test6.rb → test_advanced_widgets.rb} +0 -0
- /data/test/manual/{test1.rb → test_basic_posts.rb} +0 -0
- /data/test/manual/{test4.rb → test_layout_widgets.rb} +0 -0
- /data/test/manual/{test5.rb → test_pagination.rb} +0 -0
- /data/test/manual/{test3.rb → test_random_posts.rb} +0 -0
data/lib/scriptorium/view.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'fileutils'
|
2
|
-
require_relative 'syntax_highlighter'
|
3
2
|
|
4
3
|
class Scriptorium::View
|
5
4
|
include Scriptorium::Exceptions
|
@@ -25,10 +24,14 @@ class Scriptorium::View
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def initialize(name, title, subtitle = "", theme = "standard")
|
28
|
-
|
29
|
-
assume {
|
30
|
-
|
31
|
-
assume {
|
27
|
+
msg = "name must be a String, got #{name.class}"
|
28
|
+
assume(msg) { name.is_a?(String) }
|
29
|
+
msg = "title must be a String, got #{title.class}"
|
30
|
+
assume(msg) { title.is_a?(String) }
|
31
|
+
msg = "subtitle must be a String, got #{subtitle.class}"
|
32
|
+
assume(msg) { subtitle.is_a?(String) }
|
33
|
+
msg = "theme must be a String, got #{theme.class}"
|
34
|
+
assume(msg) { theme.is_a?(String) }
|
32
35
|
|
33
36
|
validate_name(name)
|
34
37
|
validate_title(title)
|
@@ -50,46 +53,21 @@ class Scriptorium::View
|
|
50
53
|
end
|
51
54
|
|
52
55
|
private def validate_name(name)
|
53
|
-
raise
|
56
|
+
raise ViewNameNil if name.nil?
|
54
57
|
|
55
|
-
raise
|
58
|
+
raise ViewNameEmpty if name.to_s.strip.empty?
|
56
59
|
|
57
60
|
unless name.match?(/^[a-zA-Z0-9_-]+$/)
|
58
|
-
raise
|
61
|
+
raise ViewNameInvalid(name)
|
59
62
|
end
|
60
63
|
end
|
61
64
|
|
62
65
|
private def validate_title(title)
|
63
|
-
raise
|
66
|
+
raise ViewTitleNil if title.nil?
|
64
67
|
|
65
|
-
raise
|
68
|
+
raise ViewTitleEmpty if title.to_s.strip.empty?
|
66
69
|
end
|
67
70
|
|
68
|
-
=begin
|
69
|
-
1. The theme provides layout/config/header.txt with default content instructions.
|
70
|
-
2. When the theme is applied, header.txt is copied to views/VIEW/config/.
|
71
|
-
3. A placeholder layout/header.html is created in views/VIEW/layout/ with <!-- HEADER CONTENT -->.
|
72
|
-
4. The file views/VIEW/config/header.txt is parsed to generate actual HTML.
|
73
|
-
5. That HTML replaces the placeholder and is written to views/VIEW/output/panes/header.html.
|
74
|
-
6. Later, output/panes/header.html is included when assembling views/VIEW/output/index.html.
|
75
|
-
|
76
|
-
That process is clean and logical. I see only minor points worth considering:
|
77
|
-
|
78
|
-
Copying header.txt from theme to view config/ is irreversible by design—once copied,
|
79
|
-
any theme updates won’t affect the view’s header.txt. That’s good for isolation, but
|
80
|
-
it might be worth exposing a way to “reapply” or “sync” a theme’s layout/config/
|
81
|
-
if desired.
|
82
|
-
Placeholder files like layout/header.html in layout/ may be unnecessary once
|
83
|
-
output/panes/header.html is reliably generated. If they exist solely for the
|
84
|
-
<!-- CONTENT --> tags, consider templating that in-memory instead.
|
85
|
-
You may want to enforce (or warn) if config/header.txt is missing or invalid
|
86
|
-
at generation time, to catch misconfigured views.
|
87
|
-
If you add more optional components (like navbars, banners, etc.), consider
|
88
|
-
adding light validation or doc comments to header.txt to aid future users/editors.
|
89
|
-
|
90
|
-
But overall, the process is robust and well thought-out. No major changes needed.
|
91
|
-
=end
|
92
|
-
|
93
71
|
def read_layout
|
94
72
|
layout_file = @dir/:config/"layout.txt"
|
95
73
|
|
@@ -114,7 +92,7 @@ But overall, the process is robust and well thought-out. No major changes needed
|
|
114
92
|
return unless File.exist?(layout_file)
|
115
93
|
|
116
94
|
flexing = {
|
117
|
-
header: %[id="header" class="header" style="padding: 10px;"],
|
95
|
+
header: %[id="header" class="header" style="padding: 10px; width: 100%; box-sizing: border-box;"],
|
118
96
|
footer: %[class="footer" style="background: lightgray; padding: 10px;"],
|
119
97
|
left: %[class="left" style="width: %{width}; background: #f0f0f0; padding: 10px; flex-grow: 0; flex-shrink: 0;"],
|
120
98
|
right: %[class="right" style="width: %{width}; background: #f0f0f0; padding: 10px; flex-grow: 0; flex-shrink: 0;"],
|
@@ -155,7 +133,8 @@ But overall, the process is robust and well thought-out. No major changes needed
|
|
155
133
|
|
156
134
|
def apply_theme(theme)
|
157
135
|
check_invariants
|
158
|
-
|
136
|
+
msg = "theme must be a non-empty String, got #{theme.class} (#{theme.inspect})"
|
137
|
+
assume(msg) { theme.is_a?(String) && !theme.empty? }
|
159
138
|
|
160
139
|
# check to see if ever done before?
|
161
140
|
# copy layout.txt to view
|
@@ -204,7 +183,6 @@ But overall, the process is robust and well thought-out. No major changes needed
|
|
204
183
|
|
205
184
|
def section_core(section, hash)
|
206
185
|
cfg = @dir/:config
|
207
|
-
template = @dir/:layout/"#{section}.html"
|
208
186
|
sectxt = cfg/"#{section}.txt"
|
209
187
|
|
210
188
|
# Only add placeholder if section has no real content
|
@@ -246,7 +224,6 @@ write output: write the result to output/panes/header.html
|
|
246
224
|
=end
|
247
225
|
|
248
226
|
def build_section(section, hash2 = {}, args = "")
|
249
|
-
config = @dir/:config/"#{section}.txt"
|
250
227
|
template = @dir/:layout/"#{section}.html"
|
251
228
|
output = @dir/:output/:panes/"#{section}.html"
|
252
229
|
|
@@ -265,11 +242,7 @@ write output: write the result to output/panes/header.html
|
|
265
242
|
target = content_tag(section)
|
266
243
|
temp_txt.sub!(target, core)
|
267
244
|
|
268
|
-
|
269
|
-
write_file(output, temp_txt)
|
270
|
-
rescue Errno::EACCES, Errno::ENOSPC => e
|
271
|
-
raise SectionOutputError(output, section, e.message)
|
272
|
-
end
|
245
|
+
write_file(output, temp_txt)
|
273
246
|
|
274
247
|
html = read_file(output)
|
275
248
|
html
|
@@ -292,26 +265,13 @@ write output: write the result to output/panes/header.html
|
|
292
265
|
|
293
266
|
def build_banner(arg)
|
294
267
|
# Check if this is an SVG banner request
|
295
|
-
return
|
268
|
+
return build_banner_svg("svg") if arg == "svg"
|
296
269
|
|
297
270
|
# Otherwise, treat as image filename
|
298
271
|
return build_banner_image(arg)
|
299
272
|
end
|
300
273
|
|
301
|
-
|
302
|
-
bsvg = Scriptorium::BannerSVG.new(@title, @subtitle)
|
303
|
-
|
304
|
-
# Look for svg.txt file in the view's config directory
|
305
|
-
svg_config_file = @dir/:config/"svg.txt"
|
306
|
-
if File.exist?(svg_config_file)
|
307
|
-
bsvg.parse_header_svg(svg_config_file)
|
308
|
-
else
|
309
|
-
# No svg.txt file, use defaults
|
310
|
-
bsvg.parse_header_svg
|
311
|
-
end
|
312
|
-
|
313
|
-
bsvg.get_svg
|
314
|
-
end
|
274
|
+
|
315
275
|
|
316
276
|
def build_banner_image(image_filename)
|
317
277
|
# Search for image in multiple locations
|
@@ -360,16 +320,16 @@ write output: write the result to output/panes/header.html
|
|
360
320
|
def build_banner_svg(arg)
|
361
321
|
bsvg = Scriptorium::BannerSVG.new(@title, @subtitle)
|
362
322
|
|
363
|
-
# Look for
|
364
|
-
|
365
|
-
if File.exist?(
|
366
|
-
bsvg.parse_header_svg(
|
323
|
+
# Look for svg.txt file in the view's config directory
|
324
|
+
svg_config_file = @dir/:config/"svg.txt"
|
325
|
+
if File.exist?(svg_config_file)
|
326
|
+
bsvg.parse_header_svg(svg_config_file)
|
367
327
|
else
|
368
|
-
# No
|
328
|
+
# No svg.txt file, just use defaults
|
369
329
|
bsvg.parse_header_svg
|
370
330
|
end
|
371
331
|
|
372
|
-
|
332
|
+
bsvg.get_svg
|
373
333
|
end
|
374
334
|
|
375
335
|
def build_nav(arg)
|
@@ -390,20 +350,22 @@ write output: write the result to output/panes/header.html
|
|
390
350
|
def generate_bootstrap_navbar(nav_content)
|
391
351
|
menu_items = parse_navbar_content(nav_content)
|
392
352
|
|
393
|
-
# Generate Bootstrap navbar HTML
|
353
|
+
# Generate Bootstrap navbar HTML wrapped in bootstrap-scope
|
394
354
|
html = <<~HTML
|
395
|
-
<
|
396
|
-
<
|
397
|
-
<
|
398
|
-
<
|
399
|
-
|
400
|
-
|
401
|
-
<
|
402
|
-
|
403
|
-
|
355
|
+
<div class="bootstrap-scope">
|
356
|
+
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
357
|
+
<div class="container-fluid">
|
358
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
359
|
+
<span class="navbar-toggler-icon"></span>
|
360
|
+
</button>
|
361
|
+
<div class="collapse navbar-collapse" id="navbarNav">
|
362
|
+
<ul class="navbar-nav">
|
363
|
+
#{generate_navbar_items(menu_items)}
|
364
|
+
</ul>
|
365
|
+
</div>
|
404
366
|
</div>
|
405
|
-
</
|
406
|
-
</
|
367
|
+
</nav>
|
368
|
+
</div>
|
407
369
|
HTML
|
408
370
|
|
409
371
|
html
|
@@ -467,8 +429,6 @@ write output: write the result to output/panes/header.html
|
|
467
429
|
end
|
468
430
|
|
469
431
|
def generate_dropdown_item(item)
|
470
|
-
dropdown_id = "dropdown-#{item[:label].downcase.gsub(/\s+/, '-')}"
|
471
|
-
|
472
432
|
html = <<~HTML
|
473
433
|
<li class="nav-item dropdown">
|
474
434
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
@@ -477,9 +437,7 @@ write output: write the result to output/panes/header.html
|
|
477
437
|
<ul class="dropdown-menu">
|
478
438
|
HTML
|
479
439
|
|
480
|
-
item[:children].each
|
481
|
-
html << generate_dropdown_child(child)
|
482
|
-
end
|
440
|
+
item[:children].each {|child| html << generate_dropdown_child(child) }
|
483
441
|
|
484
442
|
html << <<~HTML
|
485
443
|
</ul>
|
@@ -534,7 +492,8 @@ write output: write the result to output/panes/header.html
|
|
534
492
|
|
535
493
|
def build_widgets(arg)
|
536
494
|
check_invariants
|
537
|
-
|
495
|
+
msg = "arg must be a String, got #{arg.class}"
|
496
|
+
assume(msg) { arg.is_a?(String) }
|
538
497
|
validate_widget_arg(arg)
|
539
498
|
|
540
499
|
widgets = arg.split
|
@@ -542,27 +501,43 @@ write output: write the result to output/panes/header.html
|
|
542
501
|
widgets.each do |widget|
|
543
502
|
validate_widget_name(widget)
|
544
503
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
504
|
+
begin
|
505
|
+
widget_class = eval("Scriptorium::Widget::#{widget.capitalize}")
|
506
|
+
obj = widget_class.new(@repo, self)
|
507
|
+
obj.generate
|
508
|
+
content << obj.card
|
509
|
+
rescue => e
|
510
|
+
raise CannotBuildWidget("Failed to build widget '#{widget}': #{e.message}")
|
511
|
+
end
|
549
512
|
end
|
550
|
-
|
513
|
+
|
514
|
+
# Wrap widgets in bootstrap-scope container
|
515
|
+
if content.strip.empty?
|
516
|
+
result = content
|
517
|
+
else
|
518
|
+
result = <<~HTML
|
519
|
+
<div class="bootstrap-scope">
|
520
|
+
#{content}
|
521
|
+
</div>
|
522
|
+
HTML
|
523
|
+
end
|
524
|
+
|
525
|
+
verify { result.is_a?(String) }
|
551
526
|
check_invariants
|
552
|
-
|
527
|
+
result
|
553
528
|
end
|
554
529
|
|
555
530
|
private def validate_widget_arg(arg)
|
556
|
-
raise
|
531
|
+
raise WidgetsArgNil if arg.nil?
|
557
532
|
|
558
|
-
raise
|
533
|
+
raise WidgetsArgEmpty if arg.to_s.strip.empty?
|
559
534
|
end
|
560
535
|
|
561
536
|
private def validate_widget_name(name)
|
562
|
-
raise
|
537
|
+
raise WidgetNameNil if name.nil? || name.strip.empty?
|
563
538
|
|
564
539
|
unless name.match?(/^[a-zA-Z0-9_]+$/)
|
565
|
-
raise
|
540
|
+
raise WidgetNameInvalid(name)
|
566
541
|
end
|
567
542
|
end
|
568
543
|
|
@@ -592,9 +567,9 @@ write output: write the result to output/panes/header.html
|
|
592
567
|
args = sections["main"]
|
593
568
|
return "" unless args
|
594
569
|
html = " <!-- Section: main (output) -->\n"
|
595
|
-
html << %[ <div id="main" class="main" style="flex-grow: 1; padding:
|
570
|
+
html << %[ <div id="main" class="main" style="flex-grow: 1; padding: 5px; overflow-y: auto; position: relative; display: block;">]
|
596
571
|
# html << %[<div id="main" class="main" style="position: relative; display: flex; flex-direction: column;">\n]
|
597
|
-
html <<
|
572
|
+
html << support_data('post_index/style.css')
|
598
573
|
if view_posts.empty?
|
599
574
|
html << " <h1>No posts yet!</h1>"
|
600
575
|
else
|
@@ -607,32 +582,54 @@ write output: write the result to output/panes/header.html
|
|
607
582
|
|
608
583
|
def generate_post_index
|
609
584
|
posts = @repo.all_posts(self)
|
610
|
-
|
585
|
+
config = read_post_index_config
|
586
|
+
|
587
|
+
# Build table with configurable margin-top
|
588
|
+
margin_top = config[:"index.margin.top"] || "0px"
|
589
|
+
str = "<table width=100% cellpadding=#{config[:'entry.cellpadding']} style=\"margin-top: #{margin_top};\">"
|
590
|
+
|
611
591
|
# FIXME - many decisions to make here...
|
612
|
-
posts.each
|
613
|
-
|
614
|
-
|
592
|
+
posts.each {|post| str << post_index_entry(post) }
|
593
|
+
str << "</table>"
|
594
|
+
|
615
595
|
write_file(@dir/:output/"post_index.html", str)
|
616
596
|
end
|
617
597
|
|
618
598
|
def post_index_entry(post)
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
599
|
+
template = support_data('templates/index_entry.lt3')
|
600
|
+
config = read_post_index_config
|
601
|
+
vars = post.vars.merge(config)
|
602
|
+
|
603
|
+
# Add formatted_date to vars (use post.date to get pubdate/created fallback)
|
604
|
+
date_format = config[:"entry.date.format"] || "month dd break yyyy"
|
605
|
+
vars[:formatted_date] = format_date(date_format, post.date)
|
606
|
+
|
607
|
+
entry = substitute(vars, template)
|
625
608
|
entry
|
626
609
|
end
|
627
610
|
|
611
|
+
def read_post_index_config
|
612
|
+
# Read global defaults first
|
613
|
+
global_config_file = @repo.root/:config/"post_index_defaults.txt"
|
614
|
+
global_config = File.exist?(global_config_file) ? parse_commented_file(global_config_file) : {}
|
615
|
+
|
616
|
+
# Read view-specific overrides
|
617
|
+
view_config_file = @dir/:config/"post_index.txt"
|
618
|
+
view_config = File.exist?(view_config_file) ? parse_commented_file(view_config_file) : {}
|
619
|
+
|
620
|
+
# Merge: view config overrides global defaults
|
621
|
+
global_config.merge(view_config)
|
622
|
+
rescue => e
|
623
|
+
{}
|
624
|
+
end
|
625
|
+
|
628
626
|
def post_index_array
|
629
|
-
posts = view_posts.sort {|a,b|
|
627
|
+
posts = view_posts.sort {|a,b| post_compare(a, b) }
|
630
628
|
posts.map {|post| post_index_entry(post)}
|
631
629
|
end
|
632
630
|
|
633
631
|
def view_posts
|
634
|
-
|
635
|
-
@repo.all_posts(self).sort_by {|post| post.pubdate}
|
632
|
+
@repo.all_posts(self).sort {|a,b| post_compare(a, b)}
|
636
633
|
end
|
637
634
|
|
638
635
|
def generate_html_head(view = nil)
|
@@ -667,16 +664,16 @@ write output: write the result to output/panes/header.html
|
|
667
664
|
content << generate_bootstrap_css(view)
|
668
665
|
when "social"
|
669
666
|
content << generate_social_meta_tags(args)
|
670
|
-
when "
|
671
|
-
content <<
|
667
|
+
when "highlight"
|
668
|
+
content << generate_highlight_css(view) # loads highlight_css.txt
|
669
|
+
when "highlight_custom"
|
670
|
+
content << support_data('highlight/custom.css')
|
672
671
|
end
|
673
672
|
end
|
674
673
|
content << "</head>\n"
|
675
674
|
content
|
676
675
|
end
|
677
676
|
|
678
|
-
|
679
|
-
|
680
677
|
def get_common_js(view = nil)
|
681
678
|
global_js = @root/:config/"common.js"
|
682
679
|
view_js = @dir/:config/"common.js"
|
@@ -723,7 +720,7 @@ write output: write the result to output/panes/header.html
|
|
723
720
|
when "src"
|
724
721
|
src = args
|
725
722
|
when "rel"
|
726
|
-
rel = args
|
723
|
+
# rel = args
|
727
724
|
when "integrity"
|
728
725
|
integrity = args
|
729
726
|
when "crossorigin"
|
@@ -758,8 +755,9 @@ write output: write the result to output/panes/header.html
|
|
758
755
|
# Get the appropriate title, description, and URL
|
759
756
|
if is_post
|
760
757
|
title = post_data[:"post.title"] || @title
|
761
|
-
description = post_data[:"post.blurb"] || post_data[:"post.body"]
|
762
|
-
|
758
|
+
description = post_data[:"post.blurb"] || (post_data[:"post.body"] ? post_data[:"post.body"][0..200] : nil) || @desc || @subtitle || @title
|
759
|
+
slug = post_data[:"post.slug"] || (post_data[:"post.id"] ? slugify(post_data[:"post.id"], title) : 'post')
|
760
|
+
url = "posts/#{slug}#{slug.end_with?('.html') ? '' : '.html'}"
|
763
761
|
type = "article"
|
764
762
|
else
|
765
763
|
title = @title
|
@@ -829,17 +827,20 @@ write output: write the result to output/panes/header.html
|
|
829
827
|
# Determine post URL and title
|
830
828
|
if post_data
|
831
829
|
title = post_data[:"post.title"] || @title
|
832
|
-
|
830
|
+
slug = post_data[:"post.slug"] || slugify(post_data[:"post.id"], title)
|
831
|
+
url = "posts/#{slug}#{slug.end_with?('.html') ? '' : '.html'}"
|
833
832
|
else
|
834
833
|
title = @title
|
835
834
|
url = "index.html"
|
836
835
|
end
|
837
836
|
|
838
837
|
# Build Reddit share URL
|
838
|
+
require 'uri'
|
839
|
+
encoded_title = URI.encode_www_form_component(title)
|
839
840
|
if subreddit.empty?
|
840
|
-
reddit_url = "https://reddit.com/submit?url=#{escape_html(url)}&title=#{
|
841
|
+
reddit_url = "https://reddit.com/submit?url=#{escape_html(url)}&title=#{encoded_title}"
|
841
842
|
else
|
842
|
-
reddit_url = "https://reddit.com/r/#{subreddit}/submit?url=#{escape_html(url)}&title=#{
|
843
|
+
reddit_url = "https://reddit.com/r/#{subreddit}/submit?url=#{escape_html(url)}&title=#{encoded_title}"
|
843
844
|
end
|
844
845
|
|
845
846
|
# Determine hover text
|
@@ -847,9 +848,9 @@ write output: write the result to output/panes/header.html
|
|
847
848
|
hover_text = subreddit.empty? ? "Share on Reddit" : "Share on r/#{subreddit}"
|
848
849
|
end
|
849
850
|
|
850
|
-
# Generate button HTML
|
851
|
+
# Generate button HTML (use production-relative ../../assets for posts context)
|
851
852
|
button_html = %[<a href="#{reddit_url}" target="_blank" title="#{hover_text}" style="text-decoration: none; margin-right: 8px;">
|
852
|
-
<img src="assets/reddit
|
853
|
+
<img src="../../assets/icons/social/reddit.png" width="32" height="32" alt="Share on Reddit" style="vertical-align: middle;">
|
853
854
|
</a>]
|
854
855
|
|
855
856
|
button_html
|
@@ -870,7 +871,7 @@ write output: write the result to output/panes/header.html
|
|
870
871
|
end
|
871
872
|
|
872
873
|
def pagination_bar(group, count, nth) # nth group of total 'count'
|
873
|
-
str = %[<div style="align
|
874
|
+
str = %[<div style="text-align: center;">Pages: ]
|
874
875
|
1.upto(count) do |i|
|
875
876
|
if i == nth # 0-based
|
876
877
|
str << "<b>[#{i}]</b> "
|
@@ -883,18 +884,28 @@ write output: write the result to output/panes/header.html
|
|
883
884
|
end
|
884
885
|
|
885
886
|
def paginate_posts
|
887
|
+
config = read_post_index_config
|
886
888
|
posts = @repo.all_posts(self)
|
887
|
-
posts.sort! {|a,b|
|
888
|
-
ppp
|
889
|
+
posts.sort! {|a,b| post_compare(a, b) }
|
890
|
+
@ppp ||= config[:"posts.per.page"].to_i
|
889
891
|
pages = []
|
890
|
-
posts.each_slice(ppp).with_index do |group, i|
|
892
|
+
posts.each_slice(@ppp).with_index do |group, i|
|
891
893
|
pages << group.map {|post| post_index_entry(post) }
|
892
894
|
end
|
893
895
|
out = self.dir/:output
|
894
896
|
pages.each.with_index do |page, i|
|
895
897
|
bar = pagination_bar(page, pages.size, i+1)
|
896
|
-
|
897
|
-
|
898
|
+
# Wrap rows in a table to preserve layout (match non-paginated index)
|
899
|
+
margin_top = config[:"index.margin.top"] || "0px"
|
900
|
+
cellpadding = config[:'entry.cellpadding']
|
901
|
+
table_open = %(<table width=100% cellpadding=#{cellpadding} style="margin-top: #{margin_top};">)
|
902
|
+
table_close = %(</table>)
|
903
|
+
html = String.new
|
904
|
+
html << table_open
|
905
|
+
html << page.join
|
906
|
+
html << table_close
|
907
|
+
html << %[<div style="position: absolute; bottom: 0; width: 100%;">#{bar}</div>]
|
908
|
+
write_file(out/"page#{i+1}.html", html)
|
898
909
|
end
|
899
910
|
# Remove existing link if it exists, then create new one
|
900
911
|
post_index_link = out/"post_index.html"
|
@@ -903,17 +914,15 @@ write output: write the result to output/panes/header.html
|
|
903
914
|
end
|
904
915
|
|
905
916
|
def generate_front_page
|
906
|
-
layout_file = @dir/:config/"layout.txt"
|
907
917
|
index_file = @dir/:output/"index.html"
|
908
|
-
panes = @dir/:output/:panes
|
909
|
-
|
910
|
-
# Ensure output directory exists
|
911
918
|
FileUtils.mkdir_p(File.dirname(index_file))
|
912
919
|
|
913
920
|
html_head = generate_html_head(true)
|
914
921
|
content = build_containers
|
915
922
|
common = get_common_js
|
916
923
|
boot = generate_bootstrap_js
|
924
|
+
highlight_js = generate_highlight_js(true)
|
925
|
+
highlight_ruby_js = generate_highlight_ruby_js(true)
|
917
926
|
full_html = <<~HTML
|
918
927
|
<!DOCTYPE html>
|
919
928
|
#{html_head}
|
@@ -921,26 +930,24 @@ write output: write the result to output/panes/header.html
|
|
921
930
|
<body style="height: 100%; margin: 0; display: flex; flex-direction: column;">
|
922
931
|
#{content.strip}
|
923
932
|
#{boot.strip}
|
933
|
+
#{highlight_js.strip}
|
934
|
+
#{highlight_ruby_js.strip}
|
924
935
|
#{common.strip}
|
925
936
|
</body>
|
926
937
|
</html>
|
927
938
|
HTML
|
928
939
|
|
929
940
|
# Beautify HTML if HtmlBeautifier is available
|
930
|
-
# begin
|
931
|
-
# full_html = ::HtmlBeautifier.beautify(full_html)
|
932
|
-
# rescue NameError, LoadError => e
|
933
|
-
# # HtmlBeautifier not available, continue without beautification
|
934
|
-
# # This is not critical for functionality
|
935
|
-
# end
|
936
|
-
|
937
|
-
# Write the main index file
|
938
941
|
begin
|
939
|
-
|
940
|
-
rescue
|
941
|
-
|
942
|
+
full_html = ::HtmlBeautifier.beautify(full_html)
|
943
|
+
rescue NameError, LoadError => e
|
944
|
+
# HtmlBeautifier not available, continue without beautification
|
945
|
+
# This is not critical for functionality
|
942
946
|
end
|
943
947
|
|
948
|
+
# Write the main index file
|
949
|
+
write_file(index_file, full_html)
|
950
|
+
|
944
951
|
# Write debug file (optional, don't fail if it doesn't work)
|
945
952
|
begin
|
946
953
|
write_file("/tmp/full.html", full_html)
|
@@ -960,17 +967,55 @@ write output: write the result to output/panes/header.html
|
|
960
967
|
end
|
961
968
|
end
|
962
969
|
|
970
|
+
def generate_highlight_css(view = nil)
|
971
|
+
global_highlight = @root/:config/"highlight_css.txt"
|
972
|
+
view_highlight = @dir/:config/"highlight_css.txt"
|
973
|
+
highlight_file = view ? view_highlight : global_highlight
|
974
|
+
lines = read_commented_file(highlight_file)
|
975
|
+
href = rel = nil
|
976
|
+
lines.each do |line|
|
977
|
+
component, args = line.split(/\s+/, 2)
|
978
|
+
case component.downcase
|
979
|
+
when "href"
|
980
|
+
href = args
|
981
|
+
when "rel"
|
982
|
+
rel = args
|
983
|
+
end
|
984
|
+
end
|
985
|
+
%[<link rel="#{rel}" href="#{href}">\n]
|
986
|
+
end
|
963
987
|
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
988
|
+
def generate_highlight_js(view = nil)
|
989
|
+
global_highlight = @root/:config/"highlight_js.txt"
|
990
|
+
view_highlight = @dir/:config/"highlight_js.txt"
|
991
|
+
highlight_file = view ? view_highlight : global_highlight
|
992
|
+
lines = read_commented_file(highlight_file)
|
993
|
+
src = nil
|
994
|
+
lines.each do |line|
|
995
|
+
component, args = line.split(/\s+/, 2)
|
996
|
+
case component.downcase
|
997
|
+
when "src"
|
998
|
+
src = args
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
%[<script src="#{src}"></script>\n]
|
969
1002
|
end
|
970
1003
|
|
971
|
-
def
|
972
|
-
|
973
|
-
|
1004
|
+
def generate_highlight_ruby_js(view = nil)
|
1005
|
+
global_highlight = @root/:config/"highlight_ruby_js.txt"
|
1006
|
+
view_highlight = @dir/:config/"highlight_ruby_js.txt"
|
1007
|
+
highlight_file = view ? view_highlight : global_highlight
|
1008
|
+
lines = read_commented_file(highlight_file)
|
1009
|
+
src = nil
|
1010
|
+
lines.each do |line|
|
1011
|
+
component, args = line.split(/\s+/, 2)
|
1012
|
+
case component.downcase
|
1013
|
+
when "src"
|
1014
|
+
src = args
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
%[<script src="#{src}"></script>\n]
|
974
1018
|
end
|
975
1019
|
|
1020
|
+
|
976
1021
|
end
|
data/lib/scriptorium.rb
CHANGED
@@ -2,6 +2,7 @@ require 'livetext'
|
|
2
2
|
require 'find'
|
3
3
|
require 'htmlbeautifier'
|
4
4
|
require 'pp'
|
5
|
+
require 'digest'
|
5
6
|
|
6
7
|
require_relative "skeleton"
|
7
8
|
require_relative "scriptorium/version"
|
@@ -20,4 +21,26 @@ require_relative "scriptorium/widgets/links"
|
|
20
21
|
require_relative "scriptorium/widgets/pages"
|
21
22
|
require_relative "scriptorium/widgets/featured_posts"
|
22
23
|
require_relative "scriptorium/api"
|
23
|
-
|
24
|
+
|
25
|
+
# Main Scriptorium class that provides backward compatibility
|
26
|
+
class Scriptorium
|
27
|
+
def initialize(testmode: false)
|
28
|
+
@api = Scriptorium::API.new(testmode: testmode)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Delegate all the main operations to the API
|
32
|
+
def method_missing(method, *args, **kwargs, &block)
|
33
|
+
if @api.respond_to?(method)
|
34
|
+
@api.send(method, *args, **kwargs, &block)
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_to_missing?(method, include_private = false)
|
41
|
+
@api.respond_to?(method) || super
|
42
|
+
end
|
43
|
+
|
44
|
+
# Keep the API accessible for advanced users
|
45
|
+
attr_reader :api
|
46
|
+
end
|
data/lib/skeleton.rb
CHANGED
data/scriptorium.gemspec
CHANGED
@@ -28,6 +28,7 @@ spec = Gem::Specification.new do |s|
|
|
28
28
|
s.add_runtime_dependency 'rouge', '~> 3.25', '>= 3.25.0'
|
29
29
|
s.add_runtime_dependency 'sinatra', '~> 3.0', '>= 3.0.0'
|
30
30
|
s.add_runtime_dependency 'redd', '~> 0.8.8'
|
31
|
+
s.add_runtime_dependency 'x', '~> 1.0'
|
31
32
|
s.add_runtime_dependency 'htmlbeautifier', '~> 1.4', '>= 1.4.0'
|
32
33
|
s.add_runtime_dependency 'clipboard', '~> 1.3', '>= 1.3.6'
|
33
34
|
|
@@ -52,7 +53,7 @@ spec = Gem::Specification.new do |s|
|
|
52
53
|
s.homepage = 'https://github.com/Hal9000/scriptorium'
|
53
54
|
s.license = "Ruby"
|
54
55
|
s.post_install_message =
|
55
|
-
"\n Success! Run 'sblog'
|
56
|
+
"\n Success! Run command 'sblog help' to get started.\n "
|
56
57
|
end
|
57
58
|
|
58
59
|
spec
|