scriptorium 0.0.2 → 0.6.1
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/README.lt3 +324 -0
- data/README.md +3155 -1
- data/assets/.DS_Store +0 -0
- data/assets/README.md +44 -0
- data/assets/back-icon.png +0 -0
- data/assets/icons/facebook.svg +1 -0
- data/assets/icons/github.svg +1 -0
- data/assets/icons/instagram.svg +1 -0
- data/assets/icons/reddit.svg +1 -0
- data/assets/icons/ui/.DS_Store +0 -0
- data/assets/icons/ui/back.png +0 -0
- data/assets/icons/ui/copy.png +0 -0
- data/assets/icons/ui/down.png +0 -0
- data/assets/icons/ui/end.png +0 -0
- data/assets/icons/ui/exit.png +0 -0
- data/assets/icons/ui/foo +10 -0
- data/assets/icons/ui/home.png +0 -0
- data/assets/icons/ui/left.png +0 -0
- data/assets/icons/ui/next.png +0 -0
- data/assets/icons/ui/right.png +0 -0
- data/assets/icons/ui/start.png +0 -0
- data/assets/icons/ui/up.png +0 -0
- data/assets/icons/x.svg +1 -0
- data/assets/icons/youtube.svg +1 -0
- data/assets/samples/placeholder.svg +9 -0
- data/assets/themes/standard/favicon.svg +6 -0
- data/bin/scriptorium +1511 -0
- data/doc/README.txt +6 -0
- data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +95 -0
- data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +34 -0
- data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +50 -0
- data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +31 -0
- data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +73 -0
- data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +64 -0
- data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +70 -0
- data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
- data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +110 -0
- data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +73 -0
- data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
- data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +124 -0
- data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +46 -0
- data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +97 -0
- data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
- data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +211 -0
- data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +141 -0
- data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +211 -0
- data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +134 -0
- data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +41 -0
- data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +45 -0
- data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +30 -0
- data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +49 -0
- data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +46 -0
- data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +41 -0
- data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +41 -0
- data/doc/anti-amnesia/20250807-213025.md +116 -0
- data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +256 -0
- data/doc/banner_svg_config.md +114 -0
- data/doc/contrib.lt3 +8 -0
- data/doc/dependencies.md +281 -0
- data/doc/hacker.lt3 +5 -0
- data/doc/reddit_credentials_template.json +8 -0
- data/doc/reddit_integration.md +207 -0
- data/doc/user.lt3 +38 -0
- data/doc/user_guide_section_1.md +137 -0
- data/doc/user_guide_section_10.md +515 -0
- data/doc/user_guide_section_11.md +708 -0
- data/doc/user_guide_section_2.md +233 -0
- data/doc/user_guide_section_3.md +5 -0
- data/doc/user_guide_section_4.md +221 -0
- data/doc/user_guide_section_5.md +243 -0
- data/doc/user_guide_section_6.md +147 -0
- data/doc/user_guide_section_7.md +311 -0
- data/doc/user_guide_section_8.md +224 -0
- data/doc/user_guide_section_9.md +375 -0
- data/doc/userdoc-toc.txt +88 -0
- data/lib/rouge/lexers/livetext.rb +74 -0
- data/lib/scriptorium/api.rb +640 -0
- data/lib/scriptorium/banner_svg.rb +742 -0
- data/lib/scriptorium/contract.rb +33 -0
- data/lib/scriptorium/exceptions.rb +174 -0
- data/lib/scriptorium/helpers.rb +475 -0
- data/lib/scriptorium/post.rb +195 -0
- data/lib/scriptorium/reddit.rb +83 -0
- data/lib/scriptorium/repo.rb +624 -0
- data/lib/scriptorium/standard_files.rb +515 -0
- data/lib/scriptorium/syntax_highlighter.rb +234 -0
- data/lib/scriptorium/theme.rb +179 -0
- data/lib/scriptorium/version.rb +2 -2
- data/lib/scriptorium/view.rb +976 -0
- data/lib/scriptorium/widgets/featured_posts.rb +149 -0
- data/lib/scriptorium/widgets/links.rb +112 -0
- data/lib/scriptorium/widgets/pages.rb +133 -0
- data/lib/scriptorium/widgets/widget.rb +133 -0
- data/lib/scriptorium.rb +22 -9
- data/lib/skeleton.rb +11 -2
- data/scriptorium.gemspec +15 -4
- data/test/README.md +69 -0
- data/test/all +43 -0
- data/test/api_demo.rb +99 -0
- data/test/assets/imagenotfound.jpg +0 -0
- data/test/assets/images/.DS_Store +0 -0
- data/test/assets/images/README.md +27 -0
- data/test/assets/images/odd_aspect.png +0 -0
- data/test/assets/images/perfect.png +0 -0
- data/test/assets/images/small.png +0 -0
- data/test/assets/images/tall.png +0 -0
- data/test/assets/images/very_tall.png +0 -0
- data/test/assets/images/very_wide.png +0 -0
- data/test/assets/images/wide.png +0 -0
- data/test/assets/testbanner.jpg +0 -0
- data/test/banner_svg/simple_helpers.rb +13 -0
- data/test/banner_svg/unit.rb +768 -0
- data/test/ed_test.rb +204 -0
- data/test/integration/cursor_banner_combinations.rb +193 -0
- data/test/integration/cursor_banner_features.rb +374 -0
- data/test/integration/integration_test.rb +326 -0
- data/test/livetext_plugin_test.rb +229 -0
- data/test/manual/asset_mgmt.rb +67 -0
- data/test/manual/banner-tests/config.txt +3 -0
- data/test/manual/banner-tests/index.html +45 -0
- data/test/manual/banner-tests/test01.html +58 -0
- data/test/manual/banner-tests/test02.html +58 -0
- data/test/manual/banner-tests/test03.html +58 -0
- data/test/manual/banner-tests/test04.html +65 -0
- data/test/manual/banner-tests/test05.html +65 -0
- data/test/manual/banner-tests/test06.html +65 -0
- data/test/manual/banner-tests/test07.html +65 -0
- data/test/manual/banner-tests/test08.html +59 -0
- data/test/manual/banner-tests/test09.html +59 -0
- data/test/manual/banner-tests/test10.html +59 -0
- data/test/manual/banner-tests/test11.html +59 -0
- data/test/manual/banner-tests/test12.html +59 -0
- data/test/manual/banner-tests/test13.html +59 -0
- data/test/manual/banner-tests/test14.html +59 -0
- data/test/manual/banner-tests/test15.html +58 -0
- data/test/manual/banner-tests/test16.html +58 -0
- data/test/manual/banner-tests/test17.html +58 -0
- data/test/manual/banner-tests/test18.html +68 -0
- data/test/manual/banner-tests/test19.html +68 -0
- data/test/manual/banner-tests/test20.html +68 -0
- data/test/manual/banner-tests/test21.html +68 -0
- data/test/manual/banner-tests/test22.html +68 -0
- data/test/manual/banner-tests/test23.html +68 -0
- data/test/manual/banner-tests/test24.html +68 -0
- data/test/manual/banner-tests/test25.html +67 -0
- data/test/manual/banner_environment.rb +192 -0
- data/test/manual/deploy_symlink_demo.rb +142 -0
- data/test/manual/environment.rb +67 -0
- data/test/manual/make_banner.rb +153 -0
- data/test/manual/sample_banner_config.txt +12 -0
- data/test/manual/symlink_demo.rb +117 -0
- data/test/manual/test1.rb +47 -0
- data/test/manual/test2.rb +12 -0
- data/test/manual/test3.rb +38 -0
- data/test/manual/test4.rb +40 -0
- data/test/manual/test5.rb +24 -0
- data/test/manual/test6.rb +73 -0
- data/test/manual/test_banner_combinations.rb +120 -0
- data/test/manual/test_banner_features.rb +306 -0
- data/test/manual/test_banner_from_file.rb +150 -0
- data/test/manual/test_banner_in_header.rb +35 -0
- data/test/manual/test_code_highlighting.rb +68 -0
- data/test/manual/test_complex_header.rb +74 -0
- data/test/manual/test_empty_header.rb +32 -0
- data/test/manual/test_radial_custom.rb +58 -0
- data/test/manual/test_radial_large_radius.rb +52 -0
- data/test/manual/test_svg_debug.rb +47 -0
- data/test/manual/test_syntax_highlighting.rb +147 -0
- data/test/pages-demo/config/currentview.txt +1 -0
- data/test/pages-demo/views/demo/config/bootstrap_css.txt +5 -0
- data/test/pages-demo/views/demo/config/bootstrap_js.txt +4 -0
- data/test/pages-demo/views/demo/config/common.js +57 -0
- data/test/pages-demo/views/demo/config/footer.txt +1 -0
- data/test/pages-demo/views/demo/config/global-head.txt +8 -0
- data/test/pages-demo/views/demo/config/header.txt +1 -0
- data/test/pages-demo/views/demo/config/layout.txt +1 -0
- data/test/pages-demo/views/demo/config/left.txt +1 -0
- data/test/pages-demo/views/demo/config/main.txt +1 -0
- data/test/pages-demo/views/demo/config/right.txt +1 -0
- data/test/pages-demo/views/demo/config.txt +3 -0
- data/test/pages-demo/views/demo/output/panes/footer.html +1 -0
- data/test/pages-demo/views/demo/output/panes/header.html +1 -0
- data/test/pages-demo/views/demo/output/panes/left.html +1 -0
- data/test/pages-demo/views/demo/output/panes/main.html +1 -0
- data/test/pages-demo/views/demo/output/panes/right.html +1 -0
- data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
- data/test/rubytext/rubytext_demo_test.rb +42 -0
- data/test/rubytext/rubytext_testing_guide.md +277 -0
- data/test/run_automated_tests.rb +45 -0
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/config/common.js +57 -0
- data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +9 -0
- data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +4 -0
- data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +8 -0
- data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +6 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 +12 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt +2 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/layout.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +14 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +13 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +57 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +2 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +9 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/reddit.txt +10 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/social.txt +7 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +7 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +1 -0
- data/test/staging/.DS_Store +0 -0
- data/test/syntax_highlighting_test.lt3 +124 -0
- data/test/test_helpers.rb +230 -0
- data/test/tui_editor_integration_test.rb +296 -0
- data/test/tui_integration_test.rb +637 -0
- data/test/unit/api.rb +1056 -0
- data/test/unit/asset_management.rb +245 -0
- data/test/unit/clipboard_test.rb +60 -0
- data/test/unit/contract_test.rb +91 -0
- data/test/unit/core.rb +857 -0
- data/test/unit/deploy_test.rb +187 -0
- data/test/unit/gem_asset_management.rb +189 -0
- data/test/unit/livetext_basic.rb +69 -0
- data/test/unit/livetext_compatibility.rb +89 -0
- data/test/unit/post.rb +244 -0
- data/test/unit/read_commented_file_test.rb +276 -0
- data/test/unit/reddit_test.rb +235 -0
- data/test/unit/repo.rb +548 -0
- data/test/unit/social_test.rb +369 -0
- data/test/unit/symlink_test.rb +213 -0
- data/test/unit/view.rb +431 -0
- data/test/unit/widgets.rb +669 -0
- data/test/wizard_test.rb +123 -0
- data/ui/README.md +67 -0
- data/ui/common/lib/ui_common.rb +8 -0
- data/ui/rubytext/README.md +191 -0
- data/ui/rubytext/bin/scriptorium-rubytext +402 -0
- data/ui/rubytext/lib/rubytext_ui.rb +300 -0
- data/ui/tui/bin/scriptorium +1420 -0
- data/ui/tui/test/tui_test.rb +23 -0
- data/ui/web/app/app.rb +1378 -0
- data/ui/web/app/error_helpers.rb +150 -0
- data/ui/web/app/views/advanced_config.erb +190 -0
- data/ui/web/app/views/asset_management.erb +589 -0
- data/ui/web/app/views/banner_config.erb +200 -0
- data/ui/web/app/views/configure_view.erb +401 -0
- data/ui/web/app/views/dashboard.erb +162 -0
- data/ui/web/app/views/deploy_config.erb +146 -0
- data/ui/web/app/views/edit_pages.erb +195 -0
- data/ui/web/app/views/edit_post.erb +54 -0
- data/ui/web/app/views/error_page.erb +29 -0
- data/ui/web/app/views/header_config.erb +155 -0
- data/ui/web/app/views/layout_config.erb +147 -0
- data/ui/web/app/views/navbar_config.erb +411 -0
- data/ui/web/app/views/view_dashboard.erb +138 -0
- data/ui/web/bin/scriptorium-web +153 -0
- data/ui/web/test/web_basic_test.rb +38 -0
- data/ui/web/test_navbar.txt +7 -0
- data/ui/web/tmp/web_server.log +5 -0
- data/ui/web/tmp/web_server.pid +1 -0
- metadata +360 -5
@@ -0,0 +1,402 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Scriptorium RubyText UI
|
4
|
+
# Brings back the sophisticated interactive interface from Runeblog
|
5
|
+
|
6
|
+
require 'rubytext'
|
7
|
+
require_relative '../../lib/scriptorium'
|
8
|
+
require_relative '../lib/rubytext_ui'
|
9
|
+
|
10
|
+
# Set up environment
|
11
|
+
ENV['DBC_DISABLED'] = 'true' if ENV['RACK_ENV'] == 'test'
|
12
|
+
|
13
|
+
class ScriptoriumRubyTextUI
|
14
|
+
include Scriptorium::Exceptions
|
15
|
+
include Scriptorium::Helpers
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@repo = nil
|
19
|
+
@current_view = nil
|
20
|
+
@editor = ENV['EDITOR'] || 'vim'
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
setup_rubytext
|
25
|
+
check_repository
|
26
|
+
main_loop
|
27
|
+
rescue => e
|
28
|
+
handle_error(e)
|
29
|
+
ensure
|
30
|
+
cleanup
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def setup_rubytext
|
36
|
+
RubyText.start(:_echo, :keypad, scroll: true, fg: :blue, bg: :white)
|
37
|
+
puts fx("Scriptorium RubyText UI", :bold)
|
38
|
+
puts fx("v #{Scriptorium::VERSION}", :red)
|
39
|
+
puts
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_repository
|
43
|
+
if Scriptorium::Repo.exist?
|
44
|
+
@repo = Scriptorium::Repo.new
|
45
|
+
@current_view = @repo.current_view
|
46
|
+
puts fx("Repository found: #{@repo.root}", :green)
|
47
|
+
puts fx("Current view: #{@current_view&.name || 'None'}", :green)
|
48
|
+
else
|
49
|
+
puts fx("No repository found.", :yellow)
|
50
|
+
if yesno("Create new repository?")
|
51
|
+
create_repository
|
52
|
+
else
|
53
|
+
puts "Exiting..."
|
54
|
+
exit 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_repository
|
60
|
+
puts fx("Creating new repository...", :bold)
|
61
|
+
@repo = Scriptorium::Repo.create
|
62
|
+
puts fx("Repository created successfully!", :green)
|
63
|
+
|
64
|
+
if yesno("Create your first view?")
|
65
|
+
create_first_view
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_first_view
|
70
|
+
print "View name: "
|
71
|
+
view_name = gets.chomp.strip
|
72
|
+
return if view_name.empty?
|
73
|
+
|
74
|
+
print "View title: "
|
75
|
+
view_title = gets.chomp.strip
|
76
|
+
return if view_title.empty?
|
77
|
+
|
78
|
+
print "View subtitle (optional): "
|
79
|
+
view_subtitle = gets.chomp.strip
|
80
|
+
|
81
|
+
@current_view = @repo.create_view(view_name, view_title, view_subtitle)
|
82
|
+
puts fx("View '#{view_name}' created successfully!", :green)
|
83
|
+
end
|
84
|
+
|
85
|
+
def main_loop
|
86
|
+
loop do
|
87
|
+
display_context
|
88
|
+
command = gets.chomp.strip
|
89
|
+
|
90
|
+
case command.downcase
|
91
|
+
when 'q', 'quit', 'exit'
|
92
|
+
break
|
93
|
+
when 'h', 'help'
|
94
|
+
show_help
|
95
|
+
when 'v', 'version'
|
96
|
+
show_version
|
97
|
+
when 'new post'
|
98
|
+
create_post
|
99
|
+
when 'list posts'
|
100
|
+
list_posts
|
101
|
+
when 'list views'
|
102
|
+
list_views
|
103
|
+
when 'change view'
|
104
|
+
change_view
|
105
|
+
when 'config'
|
106
|
+
edit_config
|
107
|
+
when 'manage pages'
|
108
|
+
manage_pages
|
109
|
+
when 'manage links'
|
110
|
+
manage_links
|
111
|
+
when 'rebuild'
|
112
|
+
rebuild_view
|
113
|
+
when 'preview'
|
114
|
+
preview_view
|
115
|
+
when 'publish'
|
116
|
+
publish_view
|
117
|
+
when ''
|
118
|
+
# Empty command, continue
|
119
|
+
else
|
120
|
+
puts fx("Unknown command: #{command}", :red)
|
121
|
+
puts "Type 'help' for available commands"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def display_context
|
127
|
+
view_name = @current_view&.name || "No view"
|
128
|
+
print fx("[#{view_name}] ", :red, :bold)
|
129
|
+
end
|
130
|
+
|
131
|
+
def show_help
|
132
|
+
help_text = <<~HELP
|
133
|
+
Available Commands:
|
134
|
+
|
135
|
+
Basics:
|
136
|
+
h, help Show this help
|
137
|
+
q, quit, exit Exit the program
|
138
|
+
v, version Show version information
|
139
|
+
|
140
|
+
Posts:
|
141
|
+
new post Create a new post
|
142
|
+
list posts List posts in current view
|
143
|
+
edit <id> Edit a specific post
|
144
|
+
|
145
|
+
Views:
|
146
|
+
list views List all views
|
147
|
+
change view Change current view
|
148
|
+
new view Create a new view
|
149
|
+
|
150
|
+
Configuration:
|
151
|
+
config Edit configuration files
|
152
|
+
manage pages Manage pages widget
|
153
|
+
manage links Manage links widget
|
154
|
+
|
155
|
+
Generation:
|
156
|
+
rebuild Rebuild current view
|
157
|
+
preview Preview current view
|
158
|
+
publish Publish current view
|
159
|
+
HELP
|
160
|
+
|
161
|
+
puts help_text
|
162
|
+
end
|
163
|
+
|
164
|
+
def show_version
|
165
|
+
puts fx("Scriptorium", :bold), fx(" v #{Scriptorium::VERSION}", :red)
|
166
|
+
end
|
167
|
+
|
168
|
+
def create_post
|
169
|
+
return unless check_view
|
170
|
+
|
171
|
+
print "Post title: "
|
172
|
+
title = gets.chomp.strip
|
173
|
+
return if title.empty?
|
174
|
+
|
175
|
+
print "Post body (optional): "
|
176
|
+
body = gets.chomp.strip
|
177
|
+
|
178
|
+
draft_name = @repo.create_draft(title: title, body: body)
|
179
|
+
post_num = @repo.finish_draft(draft_name)
|
180
|
+
|
181
|
+
puts fx("Post ##{post_num} created successfully!", :green)
|
182
|
+
end
|
183
|
+
|
184
|
+
def list_posts
|
185
|
+
return unless check_view
|
186
|
+
|
187
|
+
posts = @repo.all_posts(@current_view)
|
188
|
+
if posts.empty?
|
189
|
+
puts "No posts in current view."
|
190
|
+
else
|
191
|
+
puts fx("Posts in #{@current_view.name}:", :bold)
|
192
|
+
posts.each do |post|
|
193
|
+
puts " #{post.id}: #{post.title}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def list_views
|
199
|
+
views = @repo.views
|
200
|
+
if views.empty?
|
201
|
+
puts "No views found."
|
202
|
+
else
|
203
|
+
puts fx("Available views:", :bold)
|
204
|
+
views.each do |view|
|
205
|
+
current = view == @current_view ? " (current)" : ""
|
206
|
+
puts " #{view.name}#{current}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def change_view
|
212
|
+
views = @repo.views
|
213
|
+
return puts("No views available.") if views.empty?
|
214
|
+
|
215
|
+
puts fx("Available views:", :bold)
|
216
|
+
views.each_with_index do |view, i|
|
217
|
+
current = view == @current_view ? " (current)" : ""
|
218
|
+
puts " #{i + 1}. #{view.name}#{current}"
|
219
|
+
end
|
220
|
+
|
221
|
+
print "Select view (1-#{views.length}): "
|
222
|
+
choice = gets.chomp.strip.to_i
|
223
|
+
|
224
|
+
if choice >= 1 && choice <= views.length
|
225
|
+
@current_view = views[choice - 1]
|
226
|
+
@repo.current_view = @current_view
|
227
|
+
puts fx("Switched to view: #{@current_view.name}", :green)
|
228
|
+
else
|
229
|
+
puts fx("Invalid choice.", :red)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def edit_config
|
234
|
+
config_items = {
|
235
|
+
"General Settings" => "config/general.txt",
|
236
|
+
"View Settings" => "config/view.txt",
|
237
|
+
"Theme Settings" => "config/theme.txt"
|
238
|
+
}
|
239
|
+
|
240
|
+
puts fx("Configuration files:", :bold)
|
241
|
+
config_items.each_with_index do |(name, path), i|
|
242
|
+
puts " #{i + 1}. #{name}"
|
243
|
+
end
|
244
|
+
|
245
|
+
print "Select file (1-#{config_items.length}): "
|
246
|
+
choice = gets.chomp.strip.to_i
|
247
|
+
|
248
|
+
if choice >= 1 && choice <= config_items.length
|
249
|
+
file_path = config_items.values[choice - 1]
|
250
|
+
edit_file(file_path)
|
251
|
+
else
|
252
|
+
puts fx("Invalid choice.", :red)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def manage_pages
|
257
|
+
return unless check_view
|
258
|
+
|
259
|
+
pages_dir = @current_view.dir/"pages"
|
260
|
+
files = Dir.glob("#{pages_dir}/*.html") if Dir.exist?(pages_dir)
|
261
|
+
files ||= []
|
262
|
+
|
263
|
+
menu_items = files.map { |f| File.basename(f) } + ["[New Page]", "[Cancel]"]
|
264
|
+
|
265
|
+
puts fx("Manage Pages:", :bold)
|
266
|
+
menu_items.each_with_index do |item, i|
|
267
|
+
puts " #{i + 1}. #{item}"
|
268
|
+
end
|
269
|
+
|
270
|
+
print "Select option (1-#{menu_items.length}): "
|
271
|
+
choice = gets.chomp.strip.to_i
|
272
|
+
|
273
|
+
if choice >= 1 && choice <= menu_items.length
|
274
|
+
selected = menu_items[choice - 1]
|
275
|
+
case selected
|
276
|
+
when "[New Page]"
|
277
|
+
create_new_page
|
278
|
+
when "[Cancel]"
|
279
|
+
return
|
280
|
+
else
|
281
|
+
edit_page(selected)
|
282
|
+
end
|
283
|
+
else
|
284
|
+
puts fx("Invalid choice.", :red)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def manage_links
|
289
|
+
return unless check_view
|
290
|
+
|
291
|
+
links_dir = @current_view.dir/"widgets"/"links"
|
292
|
+
list_file = links_dir/"list.txt" if Dir.exist?(links_dir)
|
293
|
+
|
294
|
+
if list_file && File.exist?(list_file)
|
295
|
+
edit_file(list_file)
|
296
|
+
else
|
297
|
+
puts "No links widget found. Create one first."
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def rebuild_view
|
302
|
+
return unless check_view
|
303
|
+
|
304
|
+
puts fx("Rebuilding view...", :bold)
|
305
|
+
@repo.generate_view(@current_view)
|
306
|
+
puts fx("View rebuilt successfully!", :green)
|
307
|
+
end
|
308
|
+
|
309
|
+
def preview_view
|
310
|
+
return unless check_view
|
311
|
+
|
312
|
+
puts fx("Starting preview server...", :bold)
|
313
|
+
# Implementation would start a local server
|
314
|
+
puts "Preview available at: http://localhost:8000"
|
315
|
+
end
|
316
|
+
|
317
|
+
def publish_view
|
318
|
+
return unless check_view
|
319
|
+
|
320
|
+
puts fx("Publishing view...", :bold)
|
321
|
+
# Implementation would handle deployment
|
322
|
+
puts fx("View published successfully!", :green)
|
323
|
+
end
|
324
|
+
|
325
|
+
def create_new_page
|
326
|
+
print "Page title: "
|
327
|
+
title = gets.chomp.strip
|
328
|
+
return if title.empty?
|
329
|
+
|
330
|
+
print "Page filename (without .html): "
|
331
|
+
filename = gets.chomp.strip
|
332
|
+
return if filename.empty?
|
333
|
+
|
334
|
+
filename += ".html" unless filename.end_with?(".html")
|
335
|
+
page_path = @current_view.dir/"pages"/filename
|
336
|
+
|
337
|
+
content = <<~HTML
|
338
|
+
<!DOCTYPE html>
|
339
|
+
<html>
|
340
|
+
<head>
|
341
|
+
<title>#{title}</title>
|
342
|
+
</head>
|
343
|
+
<body>
|
344
|
+
<h1>#{title}</h1>
|
345
|
+
<p>Page content goes here.</p>
|
346
|
+
</body>
|
347
|
+
</html>
|
348
|
+
HTML
|
349
|
+
|
350
|
+
File.write(page_path, content)
|
351
|
+
puts fx("Page '#{filename}' created successfully!", :green)
|
352
|
+
end
|
353
|
+
|
354
|
+
def edit_page(filename)
|
355
|
+
page_path = @current_view.dir/"pages"/filename
|
356
|
+
if File.exist?(page_path)
|
357
|
+
edit_file(page_path)
|
358
|
+
else
|
359
|
+
puts fx("Page '#{filename}' not found.", :red)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def edit_file(file_path)
|
364
|
+
full_path = File.expand_path(file_path, @repo.root)
|
365
|
+
if File.exist?(full_path)
|
366
|
+
system("#{@editor} #{full_path}")
|
367
|
+
else
|
368
|
+
puts fx("File '#{file_path}' not found.", :red)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def check_view
|
373
|
+
unless @current_view
|
374
|
+
puts fx("No view selected. Use 'change view' first.", :red)
|
375
|
+
return false
|
376
|
+
end
|
377
|
+
true
|
378
|
+
end
|
379
|
+
|
380
|
+
def yesno(question)
|
381
|
+
print fx("#{question} (y/n): ", :bold)
|
382
|
+
response = gets.chomp.strip.downcase
|
383
|
+
response == 'y' || response == 'yes'
|
384
|
+
end
|
385
|
+
|
386
|
+
def handle_error(error)
|
387
|
+
puts fx("Error: #{error.message}", :red)
|
388
|
+
puts error.backtrace if ENV['DEBUG']
|
389
|
+
end
|
390
|
+
|
391
|
+
def cleanup
|
392
|
+
RubyText.stop
|
393
|
+
puts "\nGoodbye!"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Main execution
|
398
|
+
if __FILE__ == $0
|
399
|
+
ui = ScriptoriumRubyTextUI.new
|
400
|
+
ui.start
|
401
|
+
end
|
402
|
+
|
@@ -0,0 +1,300 @@
|
|
1
|
+
# Scriptorium RubyText UI Library
|
2
|
+
# Common functionality for the RubyText-based interface
|
3
|
+
|
4
|
+
require 'rubytext'
|
5
|
+
require_relative '../../../lib/scriptorium'
|
6
|
+
|
7
|
+
module Scriptorium
|
8
|
+
module RubyTextUI
|
9
|
+
# Common UI utilities and helpers
|
10
|
+
|
11
|
+
def self.setup_environment
|
12
|
+
# Set up RubyText environment
|
13
|
+
RubyText.start(:_echo, :keypad, scroll: true, fg: :blue, bg: :white)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.cleanup_environment
|
17
|
+
RubyText.stop
|
18
|
+
end
|
19
|
+
|
20
|
+
# Menu system for file selection and configuration
|
21
|
+
class MenuSystem
|
22
|
+
def self.select_file(title:, files:, allow_new: false, allow_cancel: true)
|
23
|
+
menu_items = files.dup
|
24
|
+
menu_items << "[New File]" if allow_new
|
25
|
+
menu_items << "[Cancel]" if allow_cancel
|
26
|
+
|
27
|
+
puts fx(title, :bold)
|
28
|
+
menu_items.each_with_index do |item, i|
|
29
|
+
puts " #{i + 1}. #{item}"
|
30
|
+
end
|
31
|
+
|
32
|
+
print "Select option (1-#{menu_items.length}): "
|
33
|
+
choice = gets.chomp.strip.to_i
|
34
|
+
|
35
|
+
if choice >= 1 && choice <= menu_items.length
|
36
|
+
selected = menu_items[choice - 1]
|
37
|
+
case selected
|
38
|
+
when "[New File]"
|
39
|
+
:new
|
40
|
+
when "[Cancel]"
|
41
|
+
:cancel
|
42
|
+
else
|
43
|
+
selected
|
44
|
+
end
|
45
|
+
else
|
46
|
+
:invalid
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.select_from_hash(title:, items:)
|
51
|
+
puts fx(title, :bold)
|
52
|
+
items.each_with_index do |(name, path), i|
|
53
|
+
puts " #{i + 1}. #{name}"
|
54
|
+
end
|
55
|
+
|
56
|
+
print "Select option (1-#{items.length}): "
|
57
|
+
choice = gets.chomp.strip.to_i
|
58
|
+
|
59
|
+
if choice >= 1 && choice <= items.length
|
60
|
+
items.values[choice - 1]
|
61
|
+
else
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Editor integration
|
68
|
+
class Editor
|
69
|
+
def self.edit_file(file_path, editor: nil)
|
70
|
+
editor ||= ENV['EDITOR'] || 'vim'
|
71
|
+
full_path = File.expand_path(file_path)
|
72
|
+
|
73
|
+
if File.exist?(full_path)
|
74
|
+
system("#{editor} #{full_path}")
|
75
|
+
true
|
76
|
+
else
|
77
|
+
false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.create_file(file_path, content: nil, editor: nil)
|
82
|
+
full_path = File.expand_path(file_path)
|
83
|
+
dir = File.dirname(full_path)
|
84
|
+
|
85
|
+
# Create directory if it doesn't exist
|
86
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
87
|
+
|
88
|
+
# Write initial content if provided
|
89
|
+
if content
|
90
|
+
File.write(full_path, content)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Open in editor
|
94
|
+
edit_file(full_path, editor: editor)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Widget management
|
99
|
+
class WidgetManager
|
100
|
+
def self.manage_pages(view)
|
101
|
+
pages_dir = view.dir/"pages"
|
102
|
+
files = []
|
103
|
+
|
104
|
+
if Dir.exist?(pages_dir)
|
105
|
+
files = Dir.glob("#{pages_dir}/*.html").map { |f| File.basename(f) }
|
106
|
+
end
|
107
|
+
|
108
|
+
selected = MenuSystem.select_file(
|
109
|
+
title: "Manage Pages:",
|
110
|
+
files: files,
|
111
|
+
allow_new: true,
|
112
|
+
allow_cancel: true
|
113
|
+
)
|
114
|
+
|
115
|
+
case selected
|
116
|
+
when :new
|
117
|
+
create_new_page(view)
|
118
|
+
when :cancel
|
119
|
+
nil
|
120
|
+
when :invalid
|
121
|
+
puts fx("Invalid choice.", :red)
|
122
|
+
else
|
123
|
+
edit_page(view, selected)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.manage_links(view)
|
128
|
+
links_dir = view.dir/"widgets"/"links"
|
129
|
+
list_file = links_dir/"list.txt" if Dir.exist?(links_dir)
|
130
|
+
|
131
|
+
if list_file && File.exist?(list_file)
|
132
|
+
Editor.edit_file(list_file)
|
133
|
+
else
|
134
|
+
puts "No links widget found. Create one first."
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def self.create_new_page(view)
|
141
|
+
print "Page title: "
|
142
|
+
title = gets.chomp.strip
|
143
|
+
return if title.empty?
|
144
|
+
|
145
|
+
print "Page filename (without .html): "
|
146
|
+
filename = gets.chomp.strip
|
147
|
+
return if filename.empty?
|
148
|
+
|
149
|
+
filename += ".html" unless filename.end_with?(".html")
|
150
|
+
page_path = view.dir/"pages"/filename
|
151
|
+
|
152
|
+
content = <<~HTML
|
153
|
+
<!DOCTYPE html>
|
154
|
+
<html>
|
155
|
+
<head>
|
156
|
+
<title>#{title}</title>
|
157
|
+
</head>
|
158
|
+
<body>
|
159
|
+
<h1>#{title}</h1>
|
160
|
+
<p>Page content goes here.</p>
|
161
|
+
</body>
|
162
|
+
</html>
|
163
|
+
HTML
|
164
|
+
|
165
|
+
Editor.create_file(page_path, content: content)
|
166
|
+
puts fx("Page '#{filename}' created successfully!", :green)
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.edit_page(view, filename)
|
170
|
+
page_path = view.dir/"pages"/filename
|
171
|
+
if File.exist?(page_path)
|
172
|
+
Editor.edit_file(page_path)
|
173
|
+
else
|
174
|
+
puts fx("Page '#{filename}' not found.", :red)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Configuration management
|
180
|
+
class ConfigManager
|
181
|
+
def self.edit_config(repo)
|
182
|
+
config_items = {
|
183
|
+
"General Settings" => "config/general.txt",
|
184
|
+
"View Settings" => "config/view.txt",
|
185
|
+
"Theme Settings" => "config/theme.txt"
|
186
|
+
}
|
187
|
+
|
188
|
+
selected = MenuSystem.select_from_hash(
|
189
|
+
title: "Configuration files:",
|
190
|
+
items: config_items
|
191
|
+
)
|
192
|
+
|
193
|
+
if selected
|
194
|
+
full_path = File.expand_path(selected, repo.root)
|
195
|
+
Editor.edit_file(full_path)
|
196
|
+
else
|
197
|
+
puts fx("Invalid choice.", :red)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# View management
|
203
|
+
class ViewManager
|
204
|
+
def self.list_views(repo, current_view: nil)
|
205
|
+
views = repo.views
|
206
|
+
if views.empty?
|
207
|
+
puts "No views found."
|
208
|
+
else
|
209
|
+
puts fx("Available views:", :bold)
|
210
|
+
views.each do |view|
|
211
|
+
current = view == current_view ? " (current)" : ""
|
212
|
+
puts " #{view.name}#{current}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.change_view(repo, current_view: nil)
|
218
|
+
views = repo.views
|
219
|
+
return puts("No views available.") if views.empty?
|
220
|
+
|
221
|
+
puts fx("Available views:", :bold)
|
222
|
+
views.each_with_index do |view, i|
|
223
|
+
current = view == current_view ? " (current)" : ""
|
224
|
+
puts " #{i + 1}. #{view.name}#{current}"
|
225
|
+
end
|
226
|
+
|
227
|
+
print "Select view (1-#{views.length}): "
|
228
|
+
choice = gets.chomp.strip.to_i
|
229
|
+
|
230
|
+
if choice >= 1 && choice <= views.length
|
231
|
+
selected_view = views[choice - 1]
|
232
|
+
repo.current_view = selected_view
|
233
|
+
puts fx("Switched to view: #{selected_view.name}", :green)
|
234
|
+
selected_view
|
235
|
+
else
|
236
|
+
puts fx("Invalid choice.", :red)
|
237
|
+
nil
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.create_view(repo)
|
242
|
+
print "View name: "
|
243
|
+
view_name = gets.chomp.strip
|
244
|
+
return nil if view_name.empty?
|
245
|
+
|
246
|
+
print "View title: "
|
247
|
+
view_title = gets.chomp.strip
|
248
|
+
return nil if view_title.empty?
|
249
|
+
|
250
|
+
print "View subtitle (optional): "
|
251
|
+
view_subtitle = gets.chomp.strip
|
252
|
+
|
253
|
+
view = repo.create_view(view_name, view_title, view_subtitle)
|
254
|
+
puts fx("View '#{view_name}' created successfully!", :green)
|
255
|
+
view
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Post management
|
260
|
+
class PostManager
|
261
|
+
def self.list_posts(repo, view)
|
262
|
+
posts = repo.all_posts(view)
|
263
|
+
if posts.empty?
|
264
|
+
puts "No posts in current view."
|
265
|
+
else
|
266
|
+
puts fx("Posts in #{view.name}:", :bold)
|
267
|
+
posts.each do |post|
|
268
|
+
puts " #{post.id}: #{post.title}"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def self.create_post(repo, view)
|
274
|
+
print "Post title: "
|
275
|
+
title = gets.chomp.strip
|
276
|
+
return nil if title.empty?
|
277
|
+
|
278
|
+
print "Post body (optional): "
|
279
|
+
body = gets.chomp.strip
|
280
|
+
|
281
|
+
draft_name = repo.create_draft(title: title, body: body)
|
282
|
+
post_num = repo.finish_draft(draft_name)
|
283
|
+
|
284
|
+
puts fx("Post ##{post_num} created successfully!", :green)
|
285
|
+
post_num
|
286
|
+
end
|
287
|
+
|
288
|
+
def self.edit_post(repo, view, post_id)
|
289
|
+
post = repo.get_post(post_id)
|
290
|
+
if post
|
291
|
+
post_file = post.file_path
|
292
|
+
Editor.edit_file(post_file)
|
293
|
+
else
|
294
|
+
puts fx("Post ##{post_id} not found.", :red)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|