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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +234 -0
- data/assets/themes/dark.jsonc +11 -0
- data/assets/themes/minimal.jsonc +12 -0
- data/assets/themes/warm.jsonc +11 -0
- data/docs/adr/001-template-layouts.md +22 -0
- data/lib/hadar/application.rb +749 -0
- data/lib/hadar/command_palette.rb +57 -0
- data/lib/hadar/deck.rb +440 -0
- data/lib/hadar/deck_watcher.rb +33 -0
- data/lib/hadar/export/pdf.rb +150 -0
- data/lib/hadar/export/png_sequence.rb +63 -0
- data/lib/hadar/layout.rb +80 -0
- data/lib/hadar/presenter.rb +92 -0
- data/lib/hadar/renderer.rb +238 -0
- data/lib/hadar/rich_text_projection.rb +110 -0
- data/lib/hadar/rich_text_writeback.rb +377 -0
- data/lib/hadar/slide.rb +130 -0
- data/lib/hadar/slide_list.rb +74 -0
- data/lib/hadar/slot.rb +149 -0
- data/lib/hadar/theme.rb +85 -0
- data/lib/hadar/version.rb +5 -0
- data/lib/hadar.rb +31 -0
- data/sig/hadar.rbs +221 -0
- metadata +168 -0
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
|
data/lib/hadar/theme.rb
ADDED
|
@@ -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
|
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: []
|