hadar 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.
data/lib/hadar/slot.rb ADDED
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Hadar
6
+ class Slot
7
+ attr_reader :name, :nodes, :document, :slide_index
8
+
9
+ def initialize(name:, nodes:, document:, deck:, slide_index:)
10
+ @name = name.to_sym
11
+ @nodes = nodes.freeze
12
+ @document = document
13
+ @deck, @slide_index = deck, slide_index
14
+ freeze
15
+ end
16
+
17
+ def empty? = nodes.empty?
18
+
19
+ def markdown
20
+ nodes.map { |node| document.source.byteslice(node.range) }.join("\n")
21
+ end
22
+
23
+ def text
24
+ nodes.map { |node| plain_text(node) }.reject(&:empty?).join("\n")
25
+ end
26
+
27
+ def text_for(node)
28
+ raise TypeError, "node must be a Beid::Node" unless node.is_a?(Beid::Node)
29
+ raise Error, "node is not part of this slot" unless nodes.include?(node)
30
+
31
+ plain_text(node)
32
+ end
33
+
34
+ def table_rows(table: 0)
35
+ node = table_nodes.fetch(valid_index!(table, "table")) do
36
+ raise IndexError, "table index is outside the slot"
37
+ end
38
+ node.children.filter_map do |row|
39
+ row.children.map { |cell| plain_text(cell) } if row.type == :table_row
40
+ end
41
+ end
42
+
43
+ def image_path
44
+ return if nodes.empty?
45
+ raise Error, "image_path requires a slot containing exactly one image" unless nodes.one? && nodes.first.type == :image
46
+
47
+ destination = nodes.first.attributes.fetch(:destination)
48
+ destination.match?(/\Ahttps?:\/\//i) ? destination : URI::DEFAULT_PARSER.unescape(destination)
49
+ end
50
+
51
+ def resolved_image_path
52
+ path = image_path
53
+ path && @deck.resolve_image_path(path)
54
+ end
55
+
56
+ def replace_text(text)
57
+ @deck.replace_text(self, text)
58
+ end
59
+
60
+ def rich_text(editable: true)
61
+ value = RichTextProjection.new(self).build(editable: editable)
62
+ return value unless editable
63
+
64
+ deck, slide_index, name, source_document = @deck, @slide_index, @name, @document
65
+ value.on_change do |_text, rich_text|
66
+ raise Error, "rich text editor is stale; rebuild it from the current slot" unless deck.document.equal?(source_document)
67
+
68
+ deck.slide(slide_index).slot(name).replace_rich_text(rich_text)
69
+ source_document = deck.document
70
+ end
71
+ value
72
+ end
73
+
74
+ def replace_rich_text(value)
75
+ @deck.replace_rich_text(self, value)
76
+ end
77
+
78
+ def replace_image(path)
79
+ @deck.replace_image(self, path)
80
+ end
81
+
82
+ def insert_image(path, alt: nil)
83
+ @deck.insert_image(self, path, alt: alt)
84
+ end
85
+
86
+ def replace_table_cell(row:, column:, value:, table: 0)
87
+ @deck.replace_table_cell(self, row: row, column: column, value: value, table: table)
88
+ end
89
+
90
+ def replace_code(text, block: 0)
91
+ @deck.replace_code(self, text, block: block)
92
+ end
93
+
94
+ def owned_by?(deck) = @deck.equal?(deck)
95
+
96
+ private
97
+
98
+ def table_nodes = nodes.select { |node| node.type == :table }
99
+
100
+ def valid_index!(index, name)
101
+ raise TypeError, "#{name} index must be an Integer" unless index.is_a?(Integer)
102
+ raise IndexError, "#{name} index must not be negative" if index.negative?
103
+
104
+ index
105
+ end
106
+
107
+ def plain_text(node)
108
+ case node.type
109
+ when :text, :code_span then node.attributes.fetch(:text, "")
110
+ when :image then node.attributes.fetch(:label, "")
111
+ when :break, :softbreak then "\n"
112
+ when :task_checkbox then ""
113
+ when :code_block then code_block_text(node)
114
+ when :html_block then node.attributes.fetch(:text, "")
115
+ when :ordered_list, :list
116
+ node.children.each_with_index.map { |item, index| list_item_text(item, index) }.join("\n")
117
+ when :table
118
+ node.children.map { |row| plain_text(row) }.join("\n")
119
+ when :table_row
120
+ node.children.map { |cell| plain_text(cell) }.join(" | ")
121
+ when :block_quote
122
+ node.children.map { |child| plain_text(child) }.join("\n")
123
+ else node.children.map { |child| plain_text(child) }.join
124
+ end
125
+ end
126
+
127
+ def list_item_text(node, index)
128
+ marker = if node.attributes[:task]
129
+ node.attributes[:checked] ? "☑ " : "☐ "
130
+ elsif node.attributes[:ordered]
131
+ "#{node.attributes[:start] || index + 1}. "
132
+ else
133
+ "• "
134
+ end
135
+ marker + node.children.map { |child| plain_text(child) }.reject(&:empty?).join("\n")
136
+ end
137
+
138
+ def code_block_text(node)
139
+ lines = document.source.byteslice(node.range).to_s.scan(/.*?(?:\r\n|\r|\n|\z)/m).reject(&:empty?)
140
+ if node.attributes[:fence]
141
+ lines.shift
142
+ lines.pop if node.attributes[:closed]
143
+ lines.join
144
+ else
145
+ lines.map { |line| line.sub(/\A(?: |\t)/, "") }.join
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # APP-SPECIFIC: theme schema and loading remain local; Q6 found no shared three-app contract.
4
+ module Hadar
5
+ class Theme
6
+ DEFAULTS = {
7
+ "name" => "Minimal",
8
+ "colors" => {"background" => "#ffffff", "text" => "#18212b", "muted" => "#5c6875", "accent" => "#315b7c"},
9
+ "font" => {"family" => "sans-serif", "title_size" => 44, "body_size" => 26},
10
+ "spacing" => {"margin" => 64, "gap" => 24}
11
+ }.freeze
12
+ COLOR_KEYS = %w[background text muted accent].freeze
13
+ FONT_KEYS = %w[family title_size body_size].freeze
14
+ SPACING_KEYS = %w[margin gap].freeze
15
+
16
+ attr_reader :name, :colors, :font, :spacing
17
+
18
+ def self.default = builtin("minimal")
19
+
20
+ def self.builtin(name)
21
+ key = name.to_s.downcase
22
+ raise Error, "unknown built-in theme: #{name.inspect}" unless %w[minimal dark warm].include?(key)
23
+
24
+ load(File.expand_path("../../assets/themes/#{key}.jsonc", __dir__))
25
+ end
26
+
27
+ def self.load(path)
28
+ raise Error, "theme exceeds 256 KiB" if File.size(path) > 262_144
29
+
30
+ document = Kochab.parse(File.read(path, encoding: "UTF-8"))
31
+ raise Error, "invalid theme JSONC: #{document.errors.map(&:message).join('; ')}" unless document.valid?
32
+ raise Error, "theme must be a JSON object" unless document.value.is_a?(Hash)
33
+
34
+ new(document.value)
35
+ rescue Kochab::ParseError, SystemCallError => error
36
+ raise Error, error.message
37
+ end
38
+
39
+ def initialize(value)
40
+ raise TypeError, "theme must be a Hash" unless value.is_a?(Hash)
41
+
42
+ data = DEFAULTS.merge(value)
43
+ reject_unknown!(data, DEFAULTS.keys, "theme")
44
+ @name = data.fetch("name")
45
+ raise Error, "theme name must be a non-empty String" unless @name.is_a?(String) && !@name.strip.empty?
46
+ @name = @name.dup.freeze
47
+ @colors = section(data, "colors", COLOR_KEYS)
48
+ COLOR_KEYS.each do |key|
49
+ color = @colors.fetch(key)
50
+ raise Error, "invalid theme color #{key.inspect}" unless color.is_a?(String) && color.match?(/\A#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?\z/)
51
+ end
52
+ @font = section(data, "font", FONT_KEYS)
53
+ raise Error, "font family must be a String" unless @font.fetch("family").is_a?(String) && !@font.fetch("family").empty?
54
+ %w[title_size body_size].each do |key|
55
+ value = @font.fetch(key)
56
+ raise Error, "#{key} must be between 8 and 160" unless value.is_a?(Numeric) && value.finite? && value.between?(8, 160)
57
+ end
58
+ @spacing = section(data, "spacing", SPACING_KEYS)
59
+ SPACING_KEYS.each do |key|
60
+ value = @spacing.fetch(key)
61
+ raise Error, "#{key} must be between 0 and 512" unless value.is_a?(Numeric) && value.finite? && value.between?(0, 512)
62
+ end
63
+ [@colors, @font, @spacing].each do |section|
64
+ section.each { |key, value| section[key] = value.dup.freeze if value.is_a?(String) }
65
+ section.freeze
66
+ end
67
+ freeze
68
+ end
69
+
70
+ private
71
+
72
+ def section(data, key, allowed)
73
+ value = data.fetch(key, {})
74
+ raise Error, "#{key} must be a JSON object" unless value.is_a?(Hash)
75
+
76
+ reject_unknown!(value, allowed, key)
77
+ DEFAULTS.fetch(key).merge(value)
78
+ end
79
+
80
+ def reject_unknown!(hash, allowed, section)
81
+ unknown = hash.keys - allowed
82
+ raise Error, "unknown #{section} keys: #{unknown.join(', ')}" unless unknown.empty?
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hadar
4
+ VERSION = "0.1.0"
5
+ end
data/lib/hadar.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hadar/version"
4
+
5
+ require "beid"
6
+ require "antares"
7
+ require "kochab"
8
+ require "okab"
9
+ require "xamidimura"
10
+ require "zaniah"
11
+ require "zaniah/ui"
12
+
13
+ require_relative "hadar/layout"
14
+ require_relative "hadar/slot"
15
+ require_relative "hadar/rich_text_projection"
16
+ require_relative "hadar/rich_text_writeback"
17
+ require_relative "hadar/slide"
18
+ require_relative "hadar/theme"
19
+ require_relative "hadar/deck"
20
+ require_relative "hadar/deck_watcher"
21
+ require_relative "hadar/renderer"
22
+ require_relative "hadar/slide_list"
23
+ require_relative "hadar/presenter"
24
+ require_relative "hadar/application"
25
+
26
+ module Hadar
27
+ class Error < StandardError; end
28
+ end
29
+
30
+ require_relative "hadar/export/pdf"
31
+ require_relative "hadar/export/png_sequence"
data/sig/hadar.rbs ADDED
@@ -0,0 +1,221 @@
1
+ module Hadar
2
+ VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ module Layout
8
+ class Definition
9
+ attr_reader name: Symbol
10
+ attr_reader slots: Array[Symbol]
11
+ end
12
+
13
+ NAMES: Array[Symbol]
14
+
15
+ def self.fetch: (String | Symbol name) -> Definition
16
+ def self.select: (Array[Beid::Node] nodes, ?requested: String | Symbol | nil) -> Symbol
17
+ end
18
+
19
+ class Slot
20
+ attr_reader name: Symbol
21
+ attr_reader nodes: Array[Beid::Node]
22
+ attr_reader document: Beid::Document
23
+ attr_reader slide_index: Integer
24
+
25
+ def initialize: (name: String | Symbol, nodes: Array[Beid::Node], document: Beid::Document,
26
+ deck: Deck, slide_index: Integer) -> void
27
+ def empty?: () -> bool
28
+ def markdown: () -> String
29
+ def text: () -> String
30
+ def text_for: (Beid::Node node) -> String
31
+ def image_path: () -> String?
32
+ def resolved_image_path: () -> String?
33
+ def table_rows: (?table: Integer) -> Array[Array[String]]
34
+ def replace_text: (String text) -> Slot
35
+ def rich_text: (?editable: bool) -> Zaniah::UI::RichText
36
+ def replace_rich_text: (Zaniah::UI::RichText value) -> Slot
37
+ def replace_image: (String path) -> Slot
38
+ def insert_image: (String path, ?alt: String?) -> Slot
39
+ def replace_table_cell: (row: Integer, column: Integer, value: String, ?table: Integer) -> Slot
40
+ def replace_code: (String text, ?block: Integer) -> Slot
41
+ def owned_by?: (Deck deck) -> bool
42
+ end
43
+
44
+ class Slide
45
+ attr_reader index: Integer
46
+ attr_reader document: Beid::Document
47
+ attr_reader nodes: Array[Beid::Node]
48
+ attr_reader layout: Symbol
49
+ attr_reader slots: Hash[Symbol, Slot]
50
+ attr_reader theme: Theme
51
+ attr_reader notes: String?
52
+
53
+ def initialize: (index: Integer, document: Beid::Document, nodes: Array[Beid::Node],
54
+ ?layout: String | Symbol | nil, theme: Theme, deck: Deck) -> void
55
+ def slot: (String | Symbol name) -> Slot
56
+ def title: () -> String
57
+ end
58
+
59
+ class Deck
60
+ attr_reader document: Beid::Document
61
+ attr_reader slides: Array[Slide]
62
+ attr_reader theme: Theme
63
+ attr_reader source_path: String?
64
+
65
+ def initialize: (Beid::Document document, ?theme: Theme | String | nil,
66
+ ?source_path: String?) -> void
67
+ def self.parse: (String markdown, ?theme: Theme | String | nil) -> Deck
68
+ def self.open: (String path, ?theme: Theme | String | nil) -> Deck
69
+ def self.from_document: (Beid::Document document, ?theme: Theme | String | nil) -> Deck
70
+ def slide: (Integer index) -> Slide
71
+ def replace_text: (Slot slot, String text) -> Slot
72
+ def replace_rich_text: (Slot slot, Zaniah::UI::RichText value) -> Slot
73
+ def replace_table_cell: (Slot slot, row: Integer, column: Integer, value: String, ?table: Integer) -> Slot
74
+ def replace_code: (Slot slot, String text, ?block: Integer) -> Slot
75
+ def replace_image: (Slot slot, String path) -> Slot
76
+ def insert_image: (Slot slot, String path, ?alt: String?) -> Slot
77
+ def resolve_image_path: (String path) -> String
78
+ def save: (?String path, ?overwrite: bool) -> Deck
79
+ def external_change?: () -> bool
80
+ def reload_if_changed: () -> bool
81
+ def reload: () -> bool
82
+ def watch: (?latency: Numeric, ?on_reload: ^(Deck) -> untyped) -> DeckWatcher
83
+ def length: () -> Integer
84
+ alias size length
85
+ def empty?: () -> bool
86
+ end
87
+
88
+ class DeckWatcher
89
+ attr_reader deck: Deck
90
+
91
+ def initialize: (Deck deck, ?latency: Numeric, ?on_reload: ^(Deck) -> untyped) -> void
92
+ def poll: (?timeout: Numeric) -> bool
93
+ def close: () -> untyped
94
+ end
95
+
96
+ class Presenter
97
+ attr_reader deck: Deck
98
+ attr_reader current_index: Integer?
99
+ attr_reader started_at: Numeric?
100
+
101
+ def initialize: (Deck deck, ?renderer: Renderer, ?clock: ^() -> Numeric) -> void
102
+ def start: (?index: Integer?) -> Presenter
103
+ def stop: () -> Presenter
104
+ def go_to: (Integer index) -> Presenter
105
+ def started?: () -> bool
106
+ def current_slide: () -> Slide?
107
+ def next_slide: () -> Slide?
108
+ def notes: () -> String?
109
+ def elapsed_seconds: () -> Numeric
110
+ def elapsed_text: () -> String
111
+ def slide_view: () -> Zaniah::Element
112
+ def presenter_view: () -> Zaniah::Element
113
+ def reconcile!: () -> Presenter
114
+ end
115
+
116
+ class Application
117
+ attr_reader deck: Deck
118
+ attr_reader renderer: Renderer
119
+ attr_reader presenter: Presenter
120
+ attr_reader selected_index: Integer?
121
+ attr_reader selected_slot_name: Symbol?
122
+ attr_reader slide_list: SlideList
123
+ attr_reader keymap: Zaniah::Input::Keymap
124
+ attr_reader body_editor: Zaniah::UI::RichText?
125
+
126
+ def self.presenter_display: (Array[Zaniah::Platform::Display]) -> Zaniah::Platform::Display?
127
+ def self.default_keymap: (?clock: ^() -> Numeric) -> Zaniah::Input::Keymap
128
+ def initialize: (Deck deck, ?renderer: Renderer, ?clock: ^() -> Numeric, ?watch: bool,
129
+ ?keymap: Zaniah::Input::Keymap) -> void
130
+ def attach: (main_window: untyped, ?presenter_window: untyped) -> Application
131
+ def main_view: (width: Numeric, height: Numeric) -> Zaniah::Element
132
+ def command_palette: () -> CommandPalette?
133
+ def export_png_sequence: (String directory, ?width: Integer, ?height: Integer) -> Array[String]
134
+ def save: (?String path, ?overwrite: bool) -> Deck
135
+ def selected_slot: () -> Slot?
136
+ def select_slot: (String | Symbol name) -> Slot
137
+ def select_table_cell: (table: Integer, row: Integer, column: Integer) -> Array[Integer]
138
+ def select_code_block: (Integer index) -> Beid::Node
139
+ def insert_or_replace_selected_image: (String path) -> Slot
140
+ def replace_selected_table_cell: (String value) -> Slot
141
+ def replace_selected_code: (String text) -> Slot
142
+ def select_slide: (Integer index) -> Slide
143
+ def next_slide: () -> Slide?
144
+ def previous_slide: () -> Slide?
145
+ def first_slide: () -> Slide?
146
+ def last_slide: () -> Slide?
147
+ def toggle_fullscreen: (?window: :main | :presenter) -> bool
148
+ def fullscreen?: (?window: :main | :presenter) -> bool
149
+ def start_presentation: (?index: Integer?) -> Presenter
150
+ def stop_presentation: () -> Presenter
151
+ def tick: () -> bool
152
+ def run: () -> Application
153
+ def close: () -> Application
154
+ def stop: () -> Application
155
+ end
156
+
157
+ class CommandPalette
158
+ attr_reader query: String
159
+
160
+ def initialize: (Hash[String, ^(*untyped) -> untyped] commands) -> void
161
+ def query=: (String value) -> String
162
+ def matches: (?Integer limit) -> Array[Spica::Match]
163
+ def choose: (String label, ?untyped event, ?untyped context) -> untyped
164
+ end
165
+
166
+ class Theme
167
+ DEFAULTS: Hash[String, untyped]
168
+ COLOR_KEYS: Array[String]
169
+ FONT_KEYS: Array[String]
170
+ SPACING_KEYS: Array[String]
171
+
172
+ attr_reader name: String
173
+ attr_reader colors: Hash[String, String]
174
+ attr_reader font: Hash[String, String | Numeric]
175
+ attr_reader spacing: Hash[String, Numeric]
176
+
177
+ def self.default: () -> Theme
178
+ def self.builtin: (String name) -> Theme
179
+ def self.load: (String path) -> Theme
180
+ def initialize: (Hash[String, untyped] value) -> void
181
+ end
182
+
183
+ class Renderer
184
+ def initialize: () -> void
185
+ def describe: (Slide slide) -> Zaniah::Describe::Node
186
+ def build: (Slide slide) -> Zaniah::Element
187
+ def surface: (Slide slide) -> Zaniah::Describe::Surface
188
+ end
189
+
190
+ class SlideList
191
+ DEFAULT_ROW_HEIGHT: Integer
192
+
193
+ attr_reader deck: Deck
194
+ attr_reader element: Zaniah::UniformList
195
+ attr_reader selected_index: Integer?
196
+
197
+ def initialize: (Deck deck, ?renderer: Renderer, ?selected: Integer?, ?row_height: Numeric,
198
+ ?on_select: ^(Slide, Integer) -> untyped) -> void
199
+ def build: (width: Numeric, height: Numeric) -> Zaniah::UniformList
200
+ def select: (Integer index) -> Slide
201
+ end
202
+
203
+ module Export
204
+ class PNGSequence
205
+ MAX_PIXELS: Integer
206
+
207
+ def self.write: (Deck deck, String directory, ?renderer: Renderer,
208
+ ?width: Integer, ?height: Integer) -> Array[String]
209
+ end
210
+
211
+ class PDF
212
+ WIDTH: Integer
213
+ HEIGHT: Integer
214
+
215
+ def self.render: (Deck deck, ?font: String | Okab::Font | Alhena::Font | nil) -> String
216
+ def self.write: (Deck deck, String path, ?font: String | Okab::Font | Alhena::Font | nil) -> String
217
+ def initialize: (Deck deck, ?font: String | Okab::Font | Alhena::Font | nil) -> void
218
+ def render: () -> String
219
+ end
220
+ end
221
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hadar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: beid
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.1.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.1.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: antares
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.2.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.2.1
40
+ - !ruby/object:Gem::Dependency
41
+ name: kochab
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.2.0
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.2.0
54
+ - !ruby/object:Gem::Dependency
55
+ name: okab
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.1.0
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.1.0
68
+ - !ruby/object:Gem::Dependency
69
+ name: spica
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.1.0
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 0.1.0
82
+ - !ruby/object:Gem::Dependency
83
+ name: xamidimura
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 0.1.0
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.1.0
96
+ - !ruby/object:Gem::Dependency
97
+ name: zaniah
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.6.0
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.6.0
110
+ description: Projects Beid Markdown ASTs into template-based slide decks, edits supported
111
+ source-backed text, and exports PDF or PNG.
112
+ email:
113
+ - t.yudai92@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - CHANGELOG.md
119
+ - LICENSE.txt
120
+ - README.md
121
+ - assets/themes/dark.jsonc
122
+ - assets/themes/minimal.jsonc
123
+ - assets/themes/warm.jsonc
124
+ - docs/adr/001-template-layouts.md
125
+ - lib/hadar.rb
126
+ - lib/hadar/application.rb
127
+ - lib/hadar/command_palette.rb
128
+ - lib/hadar/deck.rb
129
+ - lib/hadar/deck_watcher.rb
130
+ - lib/hadar/export/pdf.rb
131
+ - lib/hadar/export/png_sequence.rb
132
+ - lib/hadar/layout.rb
133
+ - lib/hadar/presenter.rb
134
+ - lib/hadar/renderer.rb
135
+ - lib/hadar/rich_text_projection.rb
136
+ - lib/hadar/rich_text_writeback.rb
137
+ - lib/hadar/slide.rb
138
+ - lib/hadar/slide_list.rb
139
+ - lib/hadar/slot.rb
140
+ - lib/hadar/theme.rb
141
+ - lib/hadar/version.rb
142
+ - sig/hadar.rbs
143
+ homepage: https://github.com/noxdea/hadar
144
+ licenses:
145
+ - MIT
146
+ metadata:
147
+ allowed_push_host: https://rubygems.org
148
+ source_code_uri: https://github.com/noxdea/hadar/tree/main
149
+ changelog_uri: https://github.com/noxdea/hadar/blob/main/CHANGELOG.md
150
+ rubygems_mfa_required: 'true'
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: 3.2.0
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubygems_version: 4.0.16
166
+ specification_version: 4
167
+ summary: Markdown-backed presentation editor and preview
168
+ test_files: []