shoko 0.1.0
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 +7 -0
- data/.bundle/config +4 -0
- data/.bundle/config.bak +3 -0
- data/.rspec_status +42 -0
- data/.rubocop.yml +124 -0
- data/Gemfile +19 -0
- data/LICENSE +21 -0
- data/README.md +82 -0
- data/Rakefile +29 -0
- data/bin/start +15 -0
- data/lib/shoko/adapters/book_sources/document_service.rb +201 -0
- data/lib/shoko/adapters/book_sources/download_service.rb +95 -0
- data/lib/shoko/adapters/book_sources/epub/epub_resource_loader.rb +137 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/html_processor.rb +151 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/metadata_extractor.rb +53 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/entry_reader.rb +77 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/metadata_extractor.rb +67 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_context.rb +86 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_index.rb +75 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_scanner.rb +47 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_extractor.rb +46 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_label_resolver.rb +83 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_list_item.rb +55 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_result.rb +8 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_selector.rb +100 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_source_locator.rb +93 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_traversal.rb +103 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_walker.rb +56 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf_processor.rb +102 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/xhtml_content_parser.rb +661 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/xml_text_normalizer.rb +41 -0
- data/lib/shoko/adapters/book_sources/epub_document.rb +253 -0
- data/lib/shoko/adapters/book_sources/epub_finder/directory_scanner.rb +134 -0
- data/lib/shoko/adapters/book_sources/epub_finder/scanner_context.rb +28 -0
- data/lib/shoko/adapters/book_sources/epub_finder.rb +161 -0
- data/lib/shoko/adapters/book_sources/epub_importer.rb +268 -0
- data/lib/shoko/adapters/book_sources/gutendex_client.rb +150 -0
- data/lib/shoko/adapters/book_sources/library_scanner.rb +93 -0
- data/lib/shoko/adapters/book_sources/source_fingerprint.rb +57 -0
- data/lib/shoko/adapters/input/annotations/mouse_handler.rb +84 -0
- data/lib/shoko/adapters/input/command_bridge.rb +148 -0
- data/lib/shoko/adapters/input/command_factory.rb +255 -0
- data/lib/shoko/adapters/input/commands.rb +60 -0
- data/lib/shoko/adapters/input/dispatcher.rb +69 -0
- data/lib/shoko/adapters/input/input_controller.rb +250 -0
- data/lib/shoko/adapters/input/key_definitions.rb +108 -0
- data/lib/shoko/adapters/input/validators/file_path_validator.rb +81 -0
- data/lib/shoko/adapters/input/validators/terminal_size_validator.rb +76 -0
- data/lib/shoko/adapters/monitoring/logger.rb +150 -0
- data/lib/shoko/adapters/monitoring/perf_tracer.rb +183 -0
- data/lib/shoko/adapters/monitoring/performance_monitor.rb +110 -0
- data/lib/shoko/adapters/output/clipboard/clipboard_service.rb +125 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/image_builder.rb +149 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/text_wrapper.rb +149 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/tokenizer.rb +91 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler.rb +144 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/plain_lines_builder.rb +54 -0
- data/lib/shoko/adapters/output/formatting/formatting_service.rb +247 -0
- data/lib/shoko/adapters/output/formatting/wrapping_service.rb +228 -0
- data/lib/shoko/adapters/output/instrumentation_service.rb +52 -0
- data/lib/shoko/adapters/output/kitty/image_transcoder.rb +71 -0
- data/lib/shoko/adapters/output/kitty/kitty_graphics.rb +114 -0
- data/lib/shoko/adapters/output/kitty/kitty_image_renderer.rb +239 -0
- data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders.rb +139 -0
- data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders_diacritic_codepoints.txt +26 -0
- data/lib/shoko/adapters/output/notification_service.rb +58 -0
- data/lib/shoko/adapters/output/render_registry.rb +45 -0
- data/lib/shoko/adapters/output/rendering/models/line_geometry.rb +60 -0
- data/lib/shoko/adapters/output/rendering/models/page_rendering_context.rb +22 -0
- data/lib/shoko/adapters/output/rendering/models/render_params.rb +28 -0
- data/lib/shoko/adapters/output/rendering/models/rendering_context.rb +58 -0
- data/lib/shoko/adapters/output/terminal/buffer.rb +275 -0
- data/lib/shoko/adapters/output/terminal/constants/terminal_defaults.rb +11 -0
- data/lib/shoko/adapters/output/terminal/input/decoder.rb +347 -0
- data/lib/shoko/adapters/output/terminal/input.rb +161 -0
- data/lib/shoko/adapters/output/terminal/output.rb +105 -0
- data/lib/shoko/adapters/output/terminal/terminal.rb +167 -0
- data/lib/shoko/adapters/output/terminal/terminal_sanitizer.rb +243 -0
- data/lib/shoko/adapters/output/terminal/terminal_service.rb +138 -0
- data/lib/shoko/adapters/output/terminal/text_metrics.rb +273 -0
- data/lib/shoko/adapters/output/ui/builders/page_setup_builder.rb +47 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/footer_renderer.rb +80 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/geometry.rb +61 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/note_renderer.rb +86 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay_component.rb +234 -0
- data/lib/shoko/adapters/output/ui/components/annotations_overlay/list_renderer.rb +142 -0
- data/lib/shoko/adapters/output/ui/components/annotations_overlay_component.rb +185 -0
- data/lib/shoko/adapters/output/ui/components/base_component.rb +110 -0
- data/lib/shoko/adapters/output/ui/components/component_interface.rb +80 -0
- data/lib/shoko/adapters/output/ui/components/content_component.rb +61 -0
- data/lib/shoko/adapters/output/ui/components/enhanced_popup_menu.rb +191 -0
- data/lib/shoko/adapters/output/ui/components/footer_component.rb +120 -0
- data/lib/shoko/adapters/output/ui/components/header_component.rb +46 -0
- data/lib/shoko/adapters/output/ui/components/layouts/horizontal.rb +63 -0
- data/lib/shoko/adapters/output/ui/components/layouts/vertical.rb +73 -0
- data/lib/shoko/adapters/output/ui/components/main_menu_component.rb +103 -0
- data/lib/shoko/adapters/output/ui/components/reading/base_view_renderer.rb +199 -0
- data/lib/shoko/adapters/output/ui/components/reading/config_helpers.rb +42 -0
- data/lib/shoko/adapters/output/ui/components/reading/help_renderer.rb +62 -0
- data/lib/shoko/adapters/output/ui/components/reading/inline_segment_highlighter.rb +144 -0
- data/lib/shoko/adapters/output/ui/components/reading/kitty_image_line_renderer.rb +262 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_content_composer.rb +114 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_drawer.rb +87 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_geometry_builder.rb +41 -0
- data/lib/shoko/adapters/output/ui/components/reading/rendered_lines_recorder.rb +64 -0
- data/lib/shoko/adapters/output/ui/components/reading/single_view_renderer.rb +156 -0
- data/lib/shoko/adapters/output/ui/components/reading/split_view_renderer.rb +221 -0
- data/lib/shoko/adapters/output/ui/components/reading/view_renderer_factory.rb +20 -0
- data/lib/shoko/adapters/output/ui/components/reading/wrapped_lines_fetcher.rb +139 -0
- data/lib/shoko/adapters/output/ui/components/rect.rb +15 -0
- data/lib/shoko/adapters/output/ui/components/render_style.rb +84 -0
- data/lib/shoko/adapters/output/ui/components/screen_component.rb +24 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_detail_screen_component.rb +175 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_edit_screen_component.rb +221 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_editor_screen_component.rb +205 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_rendering_helpers.rb +190 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotations_screen_component.rb +266 -0
- data/lib/shoko/adapters/output/ui/components/screens/base_screen_component.rb +49 -0
- data/lib/shoko/adapters/output/ui/components/screens/browse_screen_component.rb +319 -0
- data/lib/shoko/adapters/output/ui/components/screens/download_books_screen_component.rb +340 -0
- data/lib/shoko/adapters/output/ui/components/screens/library_screen_component.rb +205 -0
- data/lib/shoko/adapters/output/ui/components/screens/loading_overlay_component.rb +49 -0
- data/lib/shoko/adapters/output/ui/components/screens/menu_screen_component.rb +107 -0
- data/lib/shoko/adapters/output/ui/components/screens/settings_screen_component.rb +238 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/annotations_tab_renderer.rb +159 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/bookmarks_tab_renderer.rb +139 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/tab_header_component.rb +157 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_renderer.rb +111 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_support.rb +1606 -0
- data/lib/shoko/adapters/output/ui/components/sidebar_panel_component.rb +217 -0
- data/lib/shoko/adapters/output/ui/components/surface.rb +88 -0
- data/lib/shoko/adapters/output/ui/components/tooltip_overlay_component.rb +224 -0
- data/lib/shoko/adapters/output/ui/components/ui/box_drawer.rb +32 -0
- data/lib/shoko/adapters/output/ui/components/ui/list_helpers.rb +33 -0
- data/lib/shoko/adapters/output/ui/components/ui/overlay_layout.rb +79 -0
- data/lib/shoko/adapters/output/ui/components/ui/text_utils.rb +46 -0
- data/lib/shoko/adapters/output/ui/constants/highlighting.rb +21 -0
- data/lib/shoko/adapters/output/ui/constants/messages.rb +12 -0
- data/lib/shoko/adapters/output/ui/constants/themes.rb +79 -0
- data/lib/shoko/adapters/output/ui/constants/ui_constants.rb +85 -0
- data/lib/shoko/adapters/output/ui/rendering/frame_coordinator.rb +42 -0
- data/lib/shoko/adapters/output/ui/rendering/reader_render_coordinator.rb +169 -0
- data/lib/shoko/adapters/output/ui/rendering/render_pipeline.rb +55 -0
- data/lib/shoko/adapters/storage/atomic_file_writer.rb +43 -0
- data/lib/shoko/adapters/storage/background_worker.rb +66 -0
- data/lib/shoko/adapters/storage/book_cache_pipeline.rb +653 -0
- data/lib/shoko/adapters/storage/cache/epub/memory_cache.rb +99 -0
- data/lib/shoko/adapters/storage/cache/epub/persistence.rb +131 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/deserialize.rb +225 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/helpers.rb +63 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/serialize.rb +83 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer.rb +5 -0
- data/lib/shoko/adapters/storage/cache/epub/source_reference.rb +58 -0
- data/lib/shoko/adapters/storage/cache_paths.rb +21 -0
- data/lib/shoko/adapters/storage/cache_pointer_manager.rb +60 -0
- data/lib/shoko/adapters/storage/config_paths.rb +30 -0
- data/lib/shoko/adapters/storage/epub_cache.rb +195 -0
- data/lib/shoko/adapters/storage/file_writer_service.rb +47 -0
- data/lib/shoko/adapters/storage/json_cache_store/chapters.rb +141 -0
- data/lib/shoko/adapters/storage/json_cache_store/layouts.rb +67 -0
- data/lib/shoko/adapters/storage/json_cache_store/manifest.rb +42 -0
- data/lib/shoko/adapters/storage/json_cache_store/payload_helpers.rb +113 -0
- data/lib/shoko/adapters/storage/json_cache_store/resources.rb +84 -0
- data/lib/shoko/adapters/storage/json_cache_store.rb +167 -0
- data/lib/shoko/adapters/storage/lazy_file_string.rb +65 -0
- data/lib/shoko/adapters/storage/pagination_cache.rb +127 -0
- data/lib/shoko/adapters/storage/recent_files.rb +78 -0
- data/lib/shoko/adapters/storage/repositories/annotation_repository.rb +182 -0
- data/lib/shoko/adapters/storage/repositories/base_repository.rb +81 -0
- data/lib/shoko/adapters/storage/repositories/bookmark_repository.rb +132 -0
- data/lib/shoko/adapters/storage/repositories/cached_library_repository.rb +129 -0
- data/lib/shoko/adapters/storage/repositories/config_repository.rb +262 -0
- data/lib/shoko/adapters/storage/repositories/progress_repository.rb +166 -0
- data/lib/shoko/adapters/storage/repositories/storage/annotation_file_store.rb +128 -0
- data/lib/shoko/adapters/storage/repositories/storage/bookmark_file_store.rb +109 -0
- data/lib/shoko/adapters/storage/repositories/storage/file_store_utils.rb +20 -0
- data/lib/shoko/adapters/storage/repositories/storage/progress_file_store.rb +59 -0
- data/lib/shoko/application/annotation_editor_overlay_session.rb +138 -0
- data/lib/shoko/application/cli.rb +134 -0
- data/lib/shoko/application/controllers/menu/input_controller.rb +189 -0
- data/lib/shoko/application/controllers/menu/state_controller.rb +642 -0
- data/lib/shoko/application/controllers/menu_controller.rb +469 -0
- data/lib/shoko/application/controllers/mouseable_reader.rb +377 -0
- data/lib/shoko/application/controllers/reader_controller.rb +449 -0
- data/lib/shoko/application/controllers/state_controller.rb +410 -0
- data/lib/shoko/application/controllers/ui_controller.rb +782 -0
- data/lib/shoko/application/dependency_container.rb +301 -0
- data/lib/shoko/application/infrastructure/event_bus.rb +80 -0
- data/lib/shoko/application/infrastructure/observer_state_store.rb +136 -0
- data/lib/shoko/application/infrastructure/state_store.rb +413 -0
- data/lib/shoko/application/main_menu/menu_progress_presenter.rb +83 -0
- data/lib/shoko/application/pending_jump_handler.rb +122 -0
- data/lib/shoko/application/reader_lifecycle.rb +65 -0
- data/lib/shoko/application/reader_startup_orchestrator.rb +113 -0
- data/lib/shoko/application/selectors/config_selectors.rb +62 -0
- data/lib/shoko/application/selectors/menu_selectors.rb +62 -0
- data/lib/shoko/application/selectors/reader_selectors.rb +186 -0
- data/lib/shoko/application/state/actions/base_action.rb +24 -0
- data/lib/shoko/application/state/actions/quit_to_menu_action.rb +16 -0
- data/lib/shoko/application/state/actions/switch_reader_mode_action.rb +22 -0
- data/lib/shoko/application/state/actions/toggle_view_mode_action.rb +31 -0
- data/lib/shoko/application/state/actions/update_annotation_editor_overlay_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_annotations_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_annotations_overlay_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_bookmarks_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_chapter_action.rb +24 -0
- data/lib/shoko/application/state/actions/update_config_action.rb +22 -0
- data/lib/shoko/application/state/actions/update_field_helpers.rb +26 -0
- data/lib/shoko/application/state/actions/update_menu_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_message_action.rb +35 -0
- data/lib/shoko/application/state/actions/update_page_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_pagination_state_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_popup_menu_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_reader_meta_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_reader_mode_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_rendered_lines_action.rb +40 -0
- data/lib/shoko/application/state/actions/update_selection_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_selections_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_sidebar_action.rb +34 -0
- data/lib/shoko/application/state/actions/update_ui_loading_action.rb +23 -0
- data/lib/shoko/application/ui/reader_view_model_builder.rb +74 -0
- data/lib/shoko/application/ui/view_models/reader_view_model.rb +177 -0
- data/lib/shoko/application/unified_application.rb +48 -0
- data/lib/shoko/application/use_cases/catalog_service.rb +117 -0
- data/lib/shoko/application/use_cases/commands/annotation_editor_commands.rb +105 -0
- data/lib/shoko/application/use_cases/commands/application_commands.rb +208 -0
- data/lib/shoko/application/use_cases/commands/base_command.rb +166 -0
- data/lib/shoko/application/use_cases/commands/bookmark_commands.rb +114 -0
- data/lib/shoko/application/use_cases/commands/conditional_navigation_commands.rb +57 -0
- data/lib/shoko/application/use_cases/commands/menu_commands.rb +170 -0
- data/lib/shoko/application/use_cases/commands/navigation_commands.rb +183 -0
- data/lib/shoko/application/use_cases/commands/reader_commands.rb +46 -0
- data/lib/shoko/application/use_cases/commands/sidebar_commands.rb +55 -0
- data/lib/shoko/application/use_cases/settings_service.rb +123 -0
- data/lib/shoko/core/events/annotation_events.rb +94 -0
- data/lib/shoko/core/events/base_domain_event.rb +169 -0
- data/lib/shoko/core/events/bookmark_events.rb +41 -0
- data/lib/shoko/core/events/domain_event_bus.rb +163 -0
- data/lib/shoko/core/events/progress_events.rb +108 -0
- data/lib/shoko/core/models/bookmark.rb +36 -0
- data/lib/shoko/core/models/bookmark_data.rb +10 -0
- data/lib/shoko/core/models/chapter.rb +25 -0
- data/lib/shoko/core/models/content_block.rb +44 -0
- data/lib/shoko/core/models/reader_settings.rb +20 -0
- data/lib/shoko/core/models/selection_anchor.rb +73 -0
- data/lib/shoko/core/models/toc_entry.rb +14 -0
- data/lib/shoko/core/ports/annotation_repository.rb +0 -0
- data/lib/shoko/core/ports/book_repository.rb +0 -0
- data/lib/shoko/core/ports/book_source.rb +0 -0
- data/lib/shoko/core/ports/bookmark_repository.rb +0 -0
- data/lib/shoko/core/ports/cache.rb +0 -0
- data/lib/shoko/core/ports/input_handler.rb +0 -0
- data/lib/shoko/core/ports/renderer.rb +0 -0
- data/lib/shoko/core/ports/storage.rb +0 -0
- data/lib/shoko/core/services/annotation_service.rb +102 -0
- data/lib/shoko/core/services/base_service.rb +60 -0
- data/lib/shoko/core/services/bookmark_service.rb +267 -0
- data/lib/shoko/core/services/coordinate_service.rb +265 -0
- data/lib/shoko/core/services/layout_service.rb +95 -0
- data/lib/shoko/core/services/navigation/absolute_change_applier.rb +96 -0
- data/lib/shoko/core/services/navigation/absolute_layout.rb +101 -0
- data/lib/shoko/core/services/navigation/absolute_strategy.rb +179 -0
- data/lib/shoko/core/services/navigation/context_builder.rb +52 -0
- data/lib/shoko/core/services/navigation/context_helpers.rb +63 -0
- data/lib/shoko/core/services/navigation/dynamic_change_applier.rb +50 -0
- data/lib/shoko/core/services/navigation/dynamic_strategy.rb +51 -0
- data/lib/shoko/core/services/navigation/image_offset_snapper.rb +150 -0
- data/lib/shoko/core/services/navigation/nav_context.rb +27 -0
- data/lib/shoko/core/services/navigation/state_updater.rb +29 -0
- data/lib/shoko/core/services/navigation/strategy_factory.rb +20 -0
- data/lib/shoko/core/services/navigation_service.rb +150 -0
- data/lib/shoko/core/services/page_calculator_service.rb +242 -0
- data/lib/shoko/core/services/pagination/internal/absolute_page_map_builder.rb +28 -0
- data/lib/shoko/core/services/pagination/internal/chapter_cache.rb +60 -0
- data/lib/shoko/core/services/pagination/internal/dynamic_page_map_builder.rb +157 -0
- data/lib/shoko/core/services/pagination/internal/layout_metrics_calculator.rb +73 -0
- data/lib/shoko/core/services/pagination/internal/page_hydrator.rb +145 -0
- data/lib/shoko/core/services/pagination/internal/pagination_workflow.rb +152 -0
- data/lib/shoko/core/services/pagination/page_info_calculator.rb +247 -0
- data/lib/shoko/core/services/pagination/pagination_cache_preloader.rb +173 -0
- data/lib/shoko/core/services/pagination/pagination_coordinator.rb +202 -0
- data/lib/shoko/core/services/pagination/pagination_orchestrator.rb +291 -0
- data/lib/shoko/core/services/pagination.rb +10 -0
- data/lib/shoko/core/services/progress_helper.rb +22 -0
- data/lib/shoko/core/services/selection_service.rb +126 -0
- data/lib/shoko/core/validator.rb +76 -0
- data/lib/shoko/shared/errors.rb +97 -0
- data/lib/shoko/shared/version.rb +5 -0
- data/lib/shoko/test_support/terminal_double.rb +175 -0
- data/lib/shoko/test_support/test_mode.rb +78 -0
- data/lib/shoko.rb +279 -0
- data/lib/zip.rb +732 -0
- data/zip.rb +5 -0
- metadata +370 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_component'
|
|
4
|
+
require_relative 'layouts/vertical'
|
|
5
|
+
require_relative 'screens/menu_screen_component'
|
|
6
|
+
require_relative 'screens/browse_screen_component'
|
|
7
|
+
require_relative 'screens/library_screen_component'
|
|
8
|
+
require_relative 'screens/settings_screen_component'
|
|
9
|
+
require_relative 'screens/download_books_screen_component'
|
|
10
|
+
require_relative 'screens/annotations_screen_component'
|
|
11
|
+
require_relative 'screens/annotation_edit_screen_component'
|
|
12
|
+
|
|
13
|
+
module Shoko
|
|
14
|
+
module Adapters::Output::Ui::Components
|
|
15
|
+
# Root component for the main menu system
|
|
16
|
+
class MainMenuComponent < BaseComponent
|
|
17
|
+
def initialize(main_menu, dependencies)
|
|
18
|
+
super()
|
|
19
|
+
@main_menu = main_menu
|
|
20
|
+
@state = main_menu.state
|
|
21
|
+
@dependencies = dependencies
|
|
22
|
+
@catalog = @main_menu.catalog
|
|
23
|
+
|
|
24
|
+
setup_screen_components
|
|
25
|
+
|
|
26
|
+
# Initialize current screen
|
|
27
|
+
@current_screen = @screen_components[:menu]
|
|
28
|
+
|
|
29
|
+
# Observe mode changes to switch active component
|
|
30
|
+
@state.add_observer(self, %i[menu mode], %i[menu selected])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def state_changed(path, _old_value, new_value)
|
|
34
|
+
return unless path == %i[menu mode]
|
|
35
|
+
|
|
36
|
+
mapped = case new_value
|
|
37
|
+
when :search then :browse
|
|
38
|
+
when :download_search, :download then :download
|
|
39
|
+
else new_value
|
|
40
|
+
end
|
|
41
|
+
@current_screen = @screen_components[mapped] || @screen_components[:menu]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def do_render(surface, bounds)
|
|
45
|
+
current_screen&.render(surface, bounds)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def preferred_height(_available_height)
|
|
49
|
+
:fill
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
attr_reader :current_screen
|
|
53
|
+
|
|
54
|
+
# Delegate screen-specific methods
|
|
55
|
+
def browse_screen
|
|
56
|
+
@screen_components[:browse]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def library_screen
|
|
60
|
+
@screen_components[:library]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# recent screen removed
|
|
64
|
+
|
|
65
|
+
def settings_screen
|
|
66
|
+
@screen_components[:settings]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def download_books_screen
|
|
70
|
+
@screen_components[:download]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def annotations_screen
|
|
74
|
+
@screen_components[:annotations]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def annotation_detail_screen
|
|
78
|
+
@screen_components[:annotation_detail]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def setup_screen_components
|
|
84
|
+
@screen_components = {
|
|
85
|
+
menu: Screens::MenuScreenComponent.new(@state),
|
|
86
|
+
browse: Screens::BrowseScreenComponent.new(@catalog, @state),
|
|
87
|
+
library: Screens::LibraryScreenComponent.new(@state, @dependencies),
|
|
88
|
+
settings: Screens::SettingsScreenComponent.new(@state, @catalog),
|
|
89
|
+
download: Screens::DownloadBooksScreenComponent.new(@state),
|
|
90
|
+
annotations: Screens::AnnotationsScreenComponent.new(@state),
|
|
91
|
+
annotation_editor: Screens::AnnotationEditScreenComponent.new(@state, @dependencies),
|
|
92
|
+
annotation_detail: Screens::AnnotationDetailScreenComponent.new(@state),
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
public
|
|
97
|
+
|
|
98
|
+
def annotation_edit_screen
|
|
99
|
+
@screen_components[:annotation_editor]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../base_component'
|
|
4
|
+
require_relative '../../../rendering/models/rendering_context.rb'
|
|
5
|
+
require_relative '../../../rendering/models/render_params.rb'
|
|
6
|
+
require_relative '../../../terminal/text_metrics.rb'
|
|
7
|
+
require_relative 'config_helpers'
|
|
8
|
+
require_relative 'line_drawer'
|
|
9
|
+
require_relative 'wrapped_lines_fetcher'
|
|
10
|
+
|
|
11
|
+
module Shoko
|
|
12
|
+
module Adapters::Output::Ui::Components
|
|
13
|
+
module Reading
|
|
14
|
+
# Base class for all view renderers.
|
|
15
|
+
#
|
|
16
|
+
# Subclasses implement `render_with_context` and can use helpers for layout,
|
|
17
|
+
# wrapped line fetching, and line drawing.
|
|
18
|
+
class BaseViewRenderer < BaseComponent
|
|
19
|
+
def initialize(dependencies)
|
|
20
|
+
super()
|
|
21
|
+
@dependencies = dependencies
|
|
22
|
+
raise ArgumentError, 'Dependencies must be provided to BaseViewRenderer' unless @dependencies
|
|
23
|
+
|
|
24
|
+
@layout_service = @dependencies.resolve(:layout_service)
|
|
25
|
+
@wrapped_lines_fetcher = WrappedLinesFetcher.new(@dependencies)
|
|
26
|
+
@line_drawer = nil
|
|
27
|
+
@last_render_key = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Standard ComponentInterface implementation
|
|
31
|
+
def do_render(surface, bounds)
|
|
32
|
+
context = create_rendering_context
|
|
33
|
+
return unless context
|
|
34
|
+
|
|
35
|
+
render_key = render_key_for(context, bounds)
|
|
36
|
+
record_geometry = rendered_lines_missing?(context.state) || render_key != @last_render_key
|
|
37
|
+
rendered_lines_buffer = record_geometry ? {} : nil
|
|
38
|
+
placed_kitty_images = {}
|
|
39
|
+
@line_drawer = LineDrawer.new(
|
|
40
|
+
dependencies: @dependencies,
|
|
41
|
+
rendered_lines_buffer: rendered_lines_buffer,
|
|
42
|
+
placed_kitty_images: placed_kitty_images,
|
|
43
|
+
record_geometry: record_geometry
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
render_with_context(surface, bounds, context)
|
|
47
|
+
|
|
48
|
+
if record_geometry
|
|
49
|
+
dispatch_rendered_lines(context.state, rendered_lines_buffer)
|
|
50
|
+
@last_render_key = render_key
|
|
51
|
+
end
|
|
52
|
+
ensure
|
|
53
|
+
@line_drawer = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# New rendering interface using context
|
|
57
|
+
def render_with_context(surface, bounds, context)
|
|
58
|
+
raise NotImplementedError, 'Subclasses must implement render_with_context method'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
protected
|
|
62
|
+
|
|
63
|
+
def layout_metrics(width, height, view_mode)
|
|
64
|
+
@layout_service.calculate_metrics(width, height, view_mode)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def adjust_for_line_spacing(height, line_spacing = :normal)
|
|
68
|
+
@layout_service.adjust_for_line_spacing(height, line_spacing)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def calculate_center_start_row(content_height, lines_count, line_spacing)
|
|
72
|
+
@layout_service.calculate_center_start_row(content_height, lines_count, line_spacing)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Compute common layout values for a given view mode.
|
|
76
|
+
# Returns `[col_width, content_height, spacing, displayable]`.
|
|
77
|
+
def compute_layout(bounds, view_mode, config)
|
|
78
|
+
col_width, content_height = layout_metrics(bounds.width, bounds.height, view_mode)
|
|
79
|
+
spacing = ConfigHelpers.line_spacing(config)
|
|
80
|
+
displayable = adjust_for_line_spacing(content_height, spacing)
|
|
81
|
+
[col_width, content_height, spacing, displayable]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Draw a vertical divider between columns (shared helper)
|
|
85
|
+
def draw_divider(surface, bounds, divider_col, start_row = 3)
|
|
86
|
+
col = divider_col.to_i
|
|
87
|
+
return if col <= 0
|
|
88
|
+
|
|
89
|
+
(start_row..[bounds.height - 1, start_row + 1].max).each do |row|
|
|
90
|
+
surface.write(
|
|
91
|
+
bounds,
|
|
92
|
+
row,
|
|
93
|
+
col,
|
|
94
|
+
"#{Shoko::Adapters::Output::Ui::Constants::UI::BORDER_PRIMARY}│#{Terminal::ANSI::RESET}"
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Shared helpers for common renderer patterns
|
|
100
|
+
def center_start_col(total_width, col_width)
|
|
101
|
+
[(total_width - col_width) / 2, 1].max
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def fetch_wrapped_lines(document:, chapter_index:, col_width:, offset:, length:)
|
|
105
|
+
@wrapped_lines_fetcher.fetch(
|
|
106
|
+
document: document,
|
|
107
|
+
chapter_index: chapter_index,
|
|
108
|
+
col_width: col_width,
|
|
109
|
+
offset: offset,
|
|
110
|
+
length: length
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def fetch_wrapped_lines_with_offset(document:, chapter_index:, col_width:, offset:, length:)
|
|
115
|
+
@wrapped_lines_fetcher.fetch_with_offset(
|
|
116
|
+
document: document,
|
|
117
|
+
chapter_index: chapter_index,
|
|
118
|
+
col_width: col_width,
|
|
119
|
+
offset: offset,
|
|
120
|
+
length: length
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def snap_offset_to_image_start(lines, offset)
|
|
125
|
+
@wrapped_lines_fetcher.snap_offset_to_image_start(lines, offset)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Shared helper to draw a list of lines with spacing and clipping considerations.
|
|
129
|
+
def draw_lines(surface, bounds, lines, params)
|
|
130
|
+
drawer = line_drawer
|
|
131
|
+
ctx = params.context
|
|
132
|
+
spacing = ctx ? ConfigHelpers.line_spacing(ctx.config) : :normal
|
|
133
|
+
lines.each_with_index do |line, idx|
|
|
134
|
+
row = params.start_row + (spacing == :relaxed ? idx * 2 : idx)
|
|
135
|
+
break if row > bounds.height - 1
|
|
136
|
+
|
|
137
|
+
drawer.draw_line(surface: surface, bounds: bounds, line: line, row: row, col: params.col_start,
|
|
138
|
+
width: params.col_width, context: ctx, column_id: params.column_id,
|
|
139
|
+
line_offset: params.line_offset + idx, page_id: params.page_id)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def create_rendering_context
|
|
146
|
+
state = @dependencies.resolve(:global_state)
|
|
147
|
+
Adapters::Output::Rendering::Models::RenderingContext.new(
|
|
148
|
+
document: safe_resolve(:document),
|
|
149
|
+
page_calculator: safe_resolve(:page_calculator),
|
|
150
|
+
state: state,
|
|
151
|
+
config: state,
|
|
152
|
+
view_model: nil
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def safe_resolve(name)
|
|
157
|
+
@dependencies.registered?(name) ? @dependencies.resolve(name) : nil
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def dispatch_rendered_lines(state, rendered_lines)
|
|
161
|
+
state&.dispatch(Shoko::Application::Actions::UpdateRenderedLinesAction.new(rendered_lines))
|
|
162
|
+
rescue StandardError
|
|
163
|
+
nil
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def render_key_for(context, bounds)
|
|
167
|
+
state = context.state
|
|
168
|
+
[
|
|
169
|
+
bounds.width,
|
|
170
|
+
bounds.height,
|
|
171
|
+
state.get(%i[reader current_chapter]),
|
|
172
|
+
state.get(%i[reader current_page_index]),
|
|
173
|
+
state.get(%i[reader left_page]),
|
|
174
|
+
state.get(%i[reader right_page]),
|
|
175
|
+
state.get(%i[reader single_page]),
|
|
176
|
+
context.view_mode,
|
|
177
|
+
context.page_numbering_mode,
|
|
178
|
+
Shoko::Application::Selectors::ConfigSelectors.line_spacing(state),
|
|
179
|
+
Shoko::Application::Selectors::ConfigSelectors.kitty_images(state),
|
|
180
|
+
context.document&.object_id
|
|
181
|
+
]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def rendered_lines_missing?(state)
|
|
185
|
+
lines = Shoko::Application::Selectors::ReaderSelectors.rendered_lines(state)
|
|
186
|
+
!lines || lines.empty?
|
|
187
|
+
rescue StandardError
|
|
188
|
+
true
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def line_drawer
|
|
192
|
+
return @line_drawer if @line_drawer
|
|
193
|
+
|
|
194
|
+
raise ArgumentError, 'LineDrawer not initialized (do_render not active)'
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shoko
|
|
4
|
+
module Adapters::Output::Ui::Components
|
|
5
|
+
module Reading
|
|
6
|
+
# Shared helpers for resolving configuration values from the state store.
|
|
7
|
+
module ConfigHelpers
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def config_store(config)
|
|
11
|
+
return config if config.respond_to?(:get)
|
|
12
|
+
return config.state if config.respond_to?(:state) && config.state.respond_to?(:get)
|
|
13
|
+
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def line_spacing(config)
|
|
18
|
+
store = config_store(config)
|
|
19
|
+
return Shoko::Core::Models::ReaderSettings::DEFAULT_LINE_SPACING unless store
|
|
20
|
+
|
|
21
|
+
Shoko::Application::Selectors::ConfigSelectors.line_spacing(store) ||
|
|
22
|
+
Shoko::Core::Models::ReaderSettings::DEFAULT_LINE_SPACING
|
|
23
|
+
rescue StandardError
|
|
24
|
+
Shoko::Core::Models::ReaderSettings::DEFAULT_LINE_SPACING
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def highlight_quotes?(store)
|
|
28
|
+
value = store&.get(%i[config highlight_quotes])
|
|
29
|
+
value.nil? || value
|
|
30
|
+
rescue StandardError
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def highlight_keywords?(store)
|
|
35
|
+
!!store&.get(%i[config highlight_keywords])
|
|
36
|
+
rescue StandardError
|
|
37
|
+
false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_view_renderer'
|
|
4
|
+
|
|
5
|
+
module Shoko
|
|
6
|
+
module Adapters::Output::Ui::Components
|
|
7
|
+
module Reading
|
|
8
|
+
# Renderer for help screen display
|
|
9
|
+
class HelpRenderer < BaseViewRenderer
|
|
10
|
+
HELP_LINES = [
|
|
11
|
+
'',
|
|
12
|
+
'Navigation Keys:',
|
|
13
|
+
' j / ↓ Scroll down',
|
|
14
|
+
' k / ↑ Scroll up',
|
|
15
|
+
' l / → Next page',
|
|
16
|
+
' h / ← Previous page',
|
|
17
|
+
' SPACE Next page',
|
|
18
|
+
' n Next chapter',
|
|
19
|
+
' p Previous chapter',
|
|
20
|
+
' g Go to beginning of chapter',
|
|
21
|
+
' G Go to end of chapter',
|
|
22
|
+
'',
|
|
23
|
+
'View Options:',
|
|
24
|
+
' v Toggle split/single view',
|
|
25
|
+
' P Toggle page numbering mode (Absolute/Dynamic)',
|
|
26
|
+
' + / - Adjust line spacing',
|
|
27
|
+
'',
|
|
28
|
+
'Features:',
|
|
29
|
+
' t Show Table of Contents',
|
|
30
|
+
' b Add a bookmark',
|
|
31
|
+
' B Show bookmarks',
|
|
32
|
+
' A Show annotations sidebar',
|
|
33
|
+
' Ctrl+A Toggle annotations overlay',
|
|
34
|
+
'',
|
|
35
|
+
'Other Keys:',
|
|
36
|
+
' ? Show/hide this help',
|
|
37
|
+
' q Quit to menu',
|
|
38
|
+
' Q Quit application',
|
|
39
|
+
'',
|
|
40
|
+
'',
|
|
41
|
+
'Press any key to return to reading...',
|
|
42
|
+
].freeze
|
|
43
|
+
|
|
44
|
+
def render_with_context(surface, bounds, _context)
|
|
45
|
+
b_height = bounds.height
|
|
46
|
+
b_width = bounds.width
|
|
47
|
+
start_row = [(b_height - HELP_LINES.size) / 2, 1].max
|
|
48
|
+
|
|
49
|
+
HELP_LINES.each_with_index do |line, idx|
|
|
50
|
+
row = start_row + idx
|
|
51
|
+
break if row >= b_height - 2
|
|
52
|
+
|
|
53
|
+
text_width = Shoko::Adapters::Output::Terminal::TextMetrics.visible_length(line)
|
|
54
|
+
col = [(b_width - text_width) / 2, 1].max
|
|
55
|
+
surface.write(bounds, row, col,
|
|
56
|
+
Shoko::Adapters::Output::Ui::Constants::UI::COLOR_TEXT_PRIMARY + line + Terminal::ANSI::RESET)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../../../../core/models/content_block.rb'
|
|
4
|
+
|
|
5
|
+
module Shoko
|
|
6
|
+
module Adapters::Output::Ui::Components
|
|
7
|
+
module Reading
|
|
8
|
+
# Applies inline quote/keyword highlighting to a list of TextSegment objects.
|
|
9
|
+
#
|
|
10
|
+
# This is used for DisplayLine rendering where segments already carry style metadata.
|
|
11
|
+
module InlineSegmentHighlighter
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def apply(segments, block_type:, highlight_quotes:, highlight_keywords:)
|
|
15
|
+
return segments unless highlightable?(segments, block_type, highlight_quotes, highlight_keywords)
|
|
16
|
+
|
|
17
|
+
text = segments_text(segments)
|
|
18
|
+
return segments if text.empty?
|
|
19
|
+
|
|
20
|
+
quote_ranges, keyword_ranges = build_ranges(text, highlight_quotes, highlight_keywords)
|
|
21
|
+
return segments if quote_ranges.empty? && keyword_ranges.empty?
|
|
22
|
+
|
|
23
|
+
applicator = RangeApplicator.new(text: text, quote_ranges: quote_ranges, keyword_ranges: keyword_ranges)
|
|
24
|
+
applicator.apply_to_segments(segments)
|
|
25
|
+
rescue StandardError
|
|
26
|
+
segments
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def match_ranges(text, pattern)
|
|
30
|
+
ranges = []
|
|
31
|
+
text.to_enum(:scan, pattern).each do
|
|
32
|
+
match = Regexp.last_match
|
|
33
|
+
next unless match
|
|
34
|
+
|
|
35
|
+
start_index = match.begin(0)
|
|
36
|
+
end_index = match.end(0)
|
|
37
|
+
ranges << (start_index...end_index) if end_index > start_index
|
|
38
|
+
end
|
|
39
|
+
ranges
|
|
40
|
+
rescue StandardError
|
|
41
|
+
[]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def in_ranges?(index, ranges)
|
|
45
|
+
idx = index.to_i
|
|
46
|
+
ranges.any? { |range| idx >= range.begin && idx < range.end }
|
|
47
|
+
rescue StandardError
|
|
48
|
+
false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def segments_text(segments)
|
|
52
|
+
segments.map { |segment| segment&.text.to_s }.join
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def highlightable?(segments, block_type, highlight_quotes, highlight_keywords)
|
|
58
|
+
return false unless highlight_quotes || highlight_keywords
|
|
59
|
+
return false if segments.empty?
|
|
60
|
+
|
|
61
|
+
block_type != :code
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def build_ranges(text, highlight_quotes, highlight_keywords)
|
|
65
|
+
quote_ranges = highlight_quotes ? match_ranges(text, Shoko::Adapters::Output::Ui::Constants::Highlighting::QUOTE_PATTERNS) : []
|
|
66
|
+
keyword_ranges = highlight_keywords ? match_ranges(text, Shoko::Adapters::Output::Ui::Constants::Highlighting::HIGHLIGHT_PATTERNS) : []
|
|
67
|
+
[quote_ranges, keyword_ranges]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module_function :highlightable?, :build_ranges
|
|
71
|
+
private_class_method :highlightable?, :build_ranges
|
|
72
|
+
|
|
73
|
+
# Applies computed range boundaries to segments and emits new TextSegment objects.
|
|
74
|
+
class RangeApplicator
|
|
75
|
+
def initialize(text:, quote_ranges:, keyword_ranges:)
|
|
76
|
+
@text = text
|
|
77
|
+
@quote_ranges = quote_ranges
|
|
78
|
+
@keyword_ranges = keyword_ranges
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def apply_to_segments(segments)
|
|
82
|
+
output = []
|
|
83
|
+
offset = 0
|
|
84
|
+
|
|
85
|
+
segments.each do |segment|
|
|
86
|
+
seg_text = segment.text.to_s
|
|
87
|
+
seg_start = offset
|
|
88
|
+
seg_end = seg_start + seg_text.length
|
|
89
|
+
offset = seg_end
|
|
90
|
+
next if seg_text.empty?
|
|
91
|
+
|
|
92
|
+
base_styles = segment.styles || {}
|
|
93
|
+
boundaries = boundaries_for_segment(seg_start, seg_end, base_styles)
|
|
94
|
+
output.concat(segment_pieces(boundaries, base_styles))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
output
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
attr_reader :text, :quote_ranges, :keyword_ranges
|
|
103
|
+
|
|
104
|
+
def boundaries_for_segment(seg_start, seg_end, base_styles)
|
|
105
|
+
boundaries = [seg_start, seg_end]
|
|
106
|
+
return boundaries if base_styles[:code]
|
|
107
|
+
|
|
108
|
+
(quote_ranges + keyword_ranges).each do |range|
|
|
109
|
+
add_boundary(boundaries, range.begin, seg_start, seg_end)
|
|
110
|
+
add_boundary(boundaries, range.end, seg_start, seg_end)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
boundaries.sort.uniq
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def add_boundary(boundaries, value, seg_start, seg_end)
|
|
117
|
+
boundaries << value if value > seg_start && value < seg_end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def segment_pieces(boundaries, base_styles)
|
|
121
|
+
boundaries.each_cons(2).filter_map do |start_index, end_index|
|
|
122
|
+
next if start_index >= end_index
|
|
123
|
+
|
|
124
|
+
piece = text[start_index...end_index].to_s
|
|
125
|
+
next if piece.empty?
|
|
126
|
+
|
|
127
|
+
styles = styles_for_piece(base_styles, start_index)
|
|
128
|
+
Shoko::Core::Models::TextSegment.new(text: piece, styles: styles)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def styles_for_piece(base_styles, index)
|
|
133
|
+
return base_styles if base_styles[:code]
|
|
134
|
+
|
|
135
|
+
styles = base_styles
|
|
136
|
+
styles = styles.merge(quote: true) if InlineSegmentHighlighter.in_ranges?(index, quote_ranges)
|
|
137
|
+
styles = styles.merge(accent: true) if InlineSegmentHighlighter.in_ranges?(index, keyword_ranges)
|
|
138
|
+
styles
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|