openstax_kitchen 11.0.0 → 11.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/Gemfile.lock +1 -1
  4. data/lib/kitchen/composite_page_element.rb +8 -0
  5. data/lib/kitchen/directions/bake_chapter_introductions/bake_chapter_objectives.rb +46 -0
  6. data/lib/kitchen/directions/bake_chapter_introductions/bake_chapter_outline.rb +14 -0
  7. data/lib/kitchen/directions/bake_chapter_introductions/main.rb +43 -0
  8. data/lib/kitchen/directions/bake_chapter_introductions/v1.rb +56 -0
  9. data/lib/kitchen/directions/bake_chapter_introductions/v2.rb +91 -0
  10. data/lib/kitchen/directions/bake_custom_sections/main.rb +14 -0
  11. data/lib/kitchen/directions/bake_custom_sections/v1.rb +42 -0
  12. data/lib/kitchen/directions/bake_equations.rb +2 -3
  13. data/lib/kitchen/directions/bake_example.rb +4 -5
  14. data/lib/kitchen/directions/bake_figure.rb +5 -3
  15. data/lib/kitchen/directions/bake_iframes/main.rb +11 -0
  16. data/lib/kitchen/directions/bake_iframes/v1.rb +25 -0
  17. data/lib/kitchen/directions/bake_index/main.rb +2 -2
  18. data/lib/kitchen/directions/bake_index/v1.rb +39 -7
  19. data/lib/kitchen/directions/bake_index/v1.xhtml.erb +3 -3
  20. data/lib/kitchen/directions/bake_injected_exercise.rb +18 -0
  21. data/lib/kitchen/directions/bake_injected_exercise_question.rb +71 -0
  22. data/lib/kitchen/directions/bake_link_placeholders.rb +15 -2
  23. data/lib/kitchen/directions/bake_lists_with_para.rb +3 -3
  24. data/lib/kitchen/directions/bake_notes/bake_autotitled_notes.rb +5 -5
  25. data/lib/kitchen/directions/bake_notes/bake_note_subtitle.rb +6 -2
  26. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/main.rb +13 -3
  27. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v1.rb +29 -25
  28. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v2.rb +22 -17
  29. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v3.rb +27 -22
  30. data/lib/kitchen/directions/bake_numbered_exercise/main.rb +3 -2
  31. data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +6 -24
  32. data/lib/kitchen/directions/bake_numbered_table/bake_table_body.rb +3 -3
  33. data/lib/kitchen/directions/bake_numbered_table/main.rb +4 -4
  34. data/lib/kitchen/directions/bake_numbered_table/v1.rb +3 -3
  35. data/lib/kitchen/directions/bake_numbered_table/v2.rb +3 -3
  36. data/lib/kitchen/directions/bake_toc.rb +1 -1
  37. data/lib/kitchen/directions/move_solutions_to_answer_key/move_solutions_from_exercise_section.rb +1 -7
  38. data/lib/kitchen/directions/move_solutions_to_answer_key/move_solutions_from_numbered_note.rb +1 -7
  39. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/contemporary_math.rb +17 -0
  40. data/lib/kitchen/element_base.rb +38 -2
  41. data/lib/kitchen/element_enumerator_base.rb +35 -0
  42. data/lib/kitchen/injected_question_element.rb +77 -0
  43. data/lib/kitchen/injected_question_element_enumerator.rb +21 -0
  44. data/lib/kitchen/selectors/base.rb +6 -0
  45. data/lib/kitchen/selectors/standard_1.rb +3 -0
  46. data/lib/kitchen/solution_element_enumerator.rb +21 -0
  47. data/lib/kitchen/version.rb +1 -1
  48. data/lib/locales/en.yml +6 -4
  49. data/lib/locales/es.yml +5 -4
  50. data/lib/locales/pl.yml +52 -7
  51. metadata +17 -6
  52. data/lib/kitchen/directions/bake_chapter_introductions/chapter_introduction.xhtml.erb +0 -0
  53. data/lib/kitchen/directions/bake_chapter_introductions.rb +0 -65
  54. data/lib/kitchen/directions/bake_notes/bake_note_iframes.rb +0 -27
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module BakeIframes
6
+ def self.v1(outer_element:)
7
+ V1.new.bake(outer_element: outer_element)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::BakeIframes
4
+ class V1
5
+ def bake(outer_element:)
6
+ iframes = outer_element.search('iframe')
7
+ return unless iframes.any?
8
+
9
+ iframes.each do |iframe|
10
+ iframe.wrap('<div class="os-has-iframe" data-type="alternatives">')
11
+ iframe.add_class('os-is-iframe')
12
+ link_ref = iframe[:src]
13
+ next unless link_ref
14
+
15
+ iframe = iframe.parent
16
+ iframe.add_class('os-has-link')
17
+ iframe.prepend(child:
18
+ <<~HTML
19
+ <a class="os-is-link" href="#{link_ref}" target="_window">#{I18n.t(:iframe_link_text)}</a>
20
+ HTML
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,8 +3,8 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeIndex
6
- def self.v1(book:)
7
- V1.new.bake(book: book)
6
+ def self.v1(book:, types: %w[main], uuid_prefix: nil)
7
+ V1.new.bake(book: book, types: types, uuid_prefix: uuid_prefix)
8
8
  end
9
9
  end
10
10
  end
@@ -65,7 +65,7 @@ module Kitchen::Directions::BakeIndex
65
65
  return -1 if force_first
66
66
  return 1 if other.force_first
67
67
 
68
- name <=> other.name
68
+ I18n.sort_strings(name, other.name)
69
69
  end
70
70
 
71
71
  protected
@@ -112,9 +112,15 @@ module Kitchen::Directions::BakeIndex
112
112
  end
113
113
  end
114
114
 
115
- def bake(book:)
115
+ def bake(book:, types: %w[main], uuid_prefix: '')
116
116
  @metadata_elements = book.metadata.children_to_keep.copy
117
- @index = Index.new
117
+ @uuid_prefix = uuid_prefix
118
+ @indexes = types.each.with_object({}) do |type, hash|
119
+ index_name = type == 'main' ? 'term' : type
120
+ hash[index_name] = Index.new
121
+ end
122
+
123
+ # Numbering of IDs doesn't depend on term type
118
124
 
119
125
  book.pages.terms.each do |term_element|
120
126
  page = term_element.ancestor(:page)
@@ -132,18 +138,44 @@ module Kitchen::Directions::BakeIndex
132
138
  add_term_to_index(term_element, page_title)
133
139
  end
134
140
 
135
- book.first('body').append(child: render(file: 'v1.xhtml.erb'))
141
+ types.each do |type|
142
+ @container_class = "os-index#{"-#{type}" unless type == 'main'}-container"
143
+ @uuid_key = "#{uuid_prefix}index#{"-#{type}" unless type == 'main'}"
144
+ @title = I18n.t("index.#{type}")
145
+
146
+ index_name = type == 'main' ? 'term' : type
147
+ @index = @indexes[index_name]
148
+
149
+ book.first('body').append(child: render(file: 'v1.xhtml.erb'))
150
+ end
136
151
  end
137
152
 
138
153
  def add_term_to_index(term_element, page_title)
139
- group_by = I18n.transliterate(term_element.text.strip[0])
154
+ type =
155
+ if !term_element.key?('index')
156
+ 'term'
157
+ elsif term_element['cxlxt:index'] == 'name'
158
+ 'name'
159
+ elsif term_element['cxlxt:index'] == 'foreign'
160
+ 'foreign'
161
+ end
162
+
163
+ if term_element.key?('reference')
164
+ term_reference = term_element['cmlnle:reference']
165
+ group_by = term_reference[0]
166
+ content = term_reference
167
+ else
168
+ group_by = I18n.transliterate(term_element.text.strip[0])
169
+ content = term_element.text
170
+ end
171
+
140
172
  group_by = I18n.t(:eob_index_symbols_group) unless group_by.match?(/[[:alpha:]]/)
141
173
  term_element['group-by'] = group_by
142
174
 
143
175
  # Add it to our index object
144
- @index.add_term(
176
+ @indexes[type].add_term(
145
177
  Term.new(
146
- text: term_element.text,
178
+ text: content,
147
179
  id: term_element.id,
148
180
  group_by: group_by,
149
181
  page_title: page_title.gsub(/\n/, '')
@@ -1,9 +1,9 @@
1
- <div class="os-eob os-index-container " data-type="composite-page" data-uuid-key="index">
1
+ <div class="os-eob <%= @container_class %>" data-type="composite-page" data-uuid-key="<%= @uuid_key %>">
2
2
  <h1 data-type="document-title">
3
- <span class="os-text"><%= I18n.t(:eob_index_title) %></span>
3
+ <span class="os-text"><%= @title %></span>
4
4
  </h1>
5
5
  <div data-type="metadata" style="display: none;">
6
- <h1 data-type="document-title" itemprop="name"><%= I18n.t(:eob_index_title) %></h1>
6
+ <h1 data-type="document-title" itemprop="name"><%= @title %></h1>
7
7
  <%= @metadata_elements.paste %>
8
8
  </div>
9
9
  <% @index.sections.each do |section| %>
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::BakeInjectedExercise
4
+ def self.v1(exercise:)
5
+ V1.new.bake(exercise: exercise)
6
+ end
7
+
8
+ class V1
9
+ def bake(exercise:)
10
+ context = exercise.search('div[data-type="exercise-context"]')&.first
11
+ return unless context
12
+
13
+ # link replacement is done by BakeLinkPlaceholders
14
+ link = context.first('a').cut
15
+ context.replace_children(with: "#{I18n.t(:context_lead_text)}#{link.paste}")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::BakeInjectedExerciseQuestion
4
+ def self.v1(question:, number:, only_number_solution: false)
5
+ V1.new.bake(question: question, number: number, only_number_solution: only_number_solution)
6
+ end
7
+
8
+ class V1
9
+ def bake(question:, number:, only_number_solution:)
10
+ id = question.id
11
+
12
+ # Store label in pantry
13
+ unless only_number_solution
14
+ label_number = "#{question.ancestor(:chapter).count_in(:book)}.#{number}"
15
+ question.target_label(label_text: 'exercise', custom_content: label_number)
16
+ end
17
+
18
+ # Synthesize multiple choice solution
19
+ if question.answers
20
+ case question.answers[:type]
21
+ when 'a'
22
+ alphabet = *('a'..'z')
23
+ else
24
+ raise('Unsupported list type for multiple choice options')
25
+ end
26
+ letter_answers = question.correct_answer_letters(alphabet)
27
+ end
28
+ if letter_answers.present?
29
+ question.append(child:
30
+ <<~HTML
31
+ <div data-type="question-solution">#{letter_answers.join(', ')}</div>
32
+ HTML
33
+ )
34
+ end
35
+
36
+ # Bake question
37
+ unless only_number_solution
38
+ problem_number = "<span class='os-number'>#{number}</span>"
39
+ if question.solution
40
+ problem_number = "<a class='os-number' href='##{id}-solution'>#{number}</a>"
41
+ end
42
+ end
43
+
44
+ question.prepend(child:
45
+ <<~HTML
46
+ #{problem_number unless only_number_solution}
47
+ #{"<span class='os-divider'>. </span>" unless only_number_solution}
48
+ <div class="os-problem-container">
49
+ #{question.stimulus&.cut&.paste}
50
+ #{question.stem.cut.paste}
51
+ #{question.answers&.cut&.paste}
52
+ </div>
53
+ HTML
54
+ )
55
+
56
+ # Bake solution
57
+ solution = question.solution
58
+ return unless solution
59
+
60
+ question.add_class('os-hasSolution')
61
+ solution.id = "#{id}-solution"
62
+ solution.replace_children(with:
63
+ <<~HTML
64
+ <a class='os-number' href='##{id}'>#{number}</a>
65
+ <span class='os-divider'>. </span>
66
+ <div class="os-solution-container">#{solution.children}</div>
67
+ HTML
68
+ )
69
+ end
70
+ end
71
+ end
@@ -5,12 +5,25 @@ module Kitchen
5
5
  # Bake directions for link placeholders
6
6
  #
7
7
  module BakeLinkPlaceholders
8
- def self.v1(book:)
8
+ def self.v1(book:, cases: false)
9
9
  book.search('a').each do |anchor|
10
10
  next unless anchor.text == '[link]'
11
11
 
12
+ label_case = anchor['cmlnle:case']
12
13
  id = anchor[:href][1..-1]
13
- replacement = book.pantry(name: :link_text).get(id)
14
+
15
+ if cases
16
+ pantry_name = if anchor.key?('case')
17
+ "#{label_case}_link_text"
18
+ else
19
+ 'nominative_link_text'
20
+ end
21
+
22
+ replacement = book.pantry(name: pantry_name).get(id)
23
+ else
24
+ replacement = book.pantry(name: :link_text).get(id)
25
+ end
26
+
14
27
  if replacement.present?
15
28
  anchor.replace_children(with: replacement)
16
29
  else
@@ -3,13 +3,13 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  # Bakes lists with paragraphs
6
- # Should be used for books created by Adaptarr to put list text directly to <li>
6
+ # Should be used for books created by Adaptarr to put list content directly to <li>
7
7
  #
8
8
  module BakeListsWithPara
9
9
  def self.v1(book:)
10
10
  book.search('li').each do |item|
11
- item_content = item.first('p')&.text
12
- item.replace_children with: item_content unless item_content.nil?
11
+ item_para = item.first('p')&.cut
12
+ item.replace_children with: item_para.children unless item_para.nil?
13
13
  end
14
14
  end
15
15
  end
@@ -3,19 +3,19 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeAutotitledNotes
6
- def self.v1(book:, classes:)
6
+ def self.v1(book:, classes:, bake_subtitle: true, cases: false)
7
7
  book.notes.each do |note|
8
8
  next unless (note.classes & classes).any?
9
9
 
10
- bake_note(note: note)
10
+ bake_note(note: note, bake_subtitle: bake_subtitle, cases: cases)
11
11
  end
12
12
  end
13
13
 
14
- def self.bake_note(note:)
15
- BakeNoteIFrames.v1(note: note)
14
+ def self.bake_note(note:, bake_subtitle:, cases: false)
15
+ Kitchen::Directions::BakeIframes.v1(outer_element: note)
16
16
  note.wrap_children(class: 'os-note-body')
17
17
 
18
- BakeNoteSubtitle.v1(note: note)
18
+ BakeNoteSubtitle.v1(note: note, cases: cases) if bake_subtitle
19
19
 
20
20
  note.prepend(child:
21
21
  <<~HTML
@@ -3,14 +3,18 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeNoteSubtitle
6
- def self.v1(note:)
6
+ def self.v1(note:, cases: false)
7
7
  title = note.title&.cut
8
8
 
9
9
  return unless title
10
10
 
11
11
  # Store label information
12
12
  note_label = title.children
13
- note.pantry(name: :link_text).store note_label, label: note.id
13
+ if cases
14
+ note.target_label(label_text: 'note', custom_content: note_label.to_s, cases: cases)
15
+ else
16
+ note.target_label(custom_content: note_label.to_s)
17
+ end
14
18
 
15
19
  title.name = 'h4'
16
20
  title.add_class('os-subtitle')
@@ -3,8 +3,8 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeNumberedNotes
6
- def self.v1(book:, classes:)
7
- V1.new.bake(book: book, classes: classes)
6
+ def self.v1(book:, classes:, cases: false)
7
+ V1.new.bake(book: book, classes: classes, cases: cases)
8
8
  end
9
9
 
10
10
  def self.v2(book:, classes:)
@@ -21,6 +21,8 @@ module Kitchen
21
21
  # Used by V1, V2, V3
22
22
  def self.bake_note_exercise(note:, exercise:, divider: ' ', suppress_solution: false)
23
23
  exercise.add_class('unnumbered')
24
+ number = note.first('.os-number').text.gsub(/#/, '')
25
+
24
26
  # bake problem
25
27
  exercise.problem.wrap_children('div', class: 'os-problem-container')
26
28
  exercise.search('[data-type="commentary"]').each(&:trash)
@@ -33,11 +35,19 @@ module Kitchen
33
35
  else
34
36
  BakeNumberedExercise.bake_solution_v1(
35
37
  exercise: exercise,
36
- number: note.first('.os-number').text.gsub(/#/, ''),
38
+ number: number,
37
39
  divider: divider
38
40
  )
39
41
  end
40
42
  end
43
+
44
+ def self.bake_note_injected_question(note:, question:)
45
+ question.add_class('unnumbered')
46
+ number = note.first('.os-number').text.gsub(/#/, '')
47
+ Kitchen::Directions::BakeInjectedExerciseQuestion.v1(
48
+ question: question, number: number, only_number_solution: true
49
+ )
50
+ end
41
51
  end
42
52
  end
43
53
  end
@@ -1,37 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Kitchen::Directions::BakeNumberedNotes
4
-
5
- class V1
6
- def bake(book:, classes:)
7
- classes.each do |klass|
8
- book.chapters.notes("$.#{klass}").each do |note|
9
- bake_note(note: note)
10
- note.exercises.each do |exercise|
11
- Kitchen::Directions::BakeNumberedNotes.bake_note_exercise(note: note, exercise: exercise)
3
+ module Kitchen::Directions
4
+ module BakeNumberedNotes
5
+ class V1
6
+ def bake(book:, classes:, cases: false)
7
+ classes.each do |klass|
8
+ book.chapters.pages.notes("$.#{klass}").each do |note|
9
+ bake_note(note: note, cases: cases)
10
+ note.exercises.each do |exercise|
11
+ BakeNumberedNotes.bake_note_exercise(note: note, exercise: exercise)
12
+ end
13
+ note.injected_questions.each do |question|
14
+ BakeNumberedNotes.bake_note_injected_question(note: note, question: question)
15
+ end
12
16
  end
13
17
  end
14
18
  end
15
- end
16
19
 
17
- def bake_note(note:)
18
- note.wrap_children(class: 'os-note-body')
20
+ def bake_note(note:, cases: false)
21
+ note.wrap_children(class: 'os-note-body')
19
22
 
20
- chapter_count = note.ancestor(:chapter).count_in(:book)
21
- note_count = note.count_in(:chapter)
22
- note.prepend(child:
23
- <<~HTML
24
- <h3 class="os-title">
25
- <span class="os-title-label">#{note.autogenerated_title}</span>
26
- <span class="os-number">#{chapter_count}.#{note_count}</span>
27
- <span class="os-divider"> </span>
28
- </h3>
29
- HTML
30
- )
23
+ chapter_count = note.ancestor(:chapter).count_in(:book)
24
+ note_count = note.count_in(:chapter)
25
+ note.prepend(child:
26
+ <<~HTML
27
+ <h3 class="os-title">
28
+ <span class="os-title-label">#{note.autogenerated_title}</span>
29
+ <span class="os-number">#{chapter_count}.#{note_count}</span>
30
+ <span class="os-divider"> </span>
31
+ </h3>
32
+ HTML
33
+ )
31
34
 
32
- return unless note['use-subtitle']
35
+ return unless note['use-subtitle']
33
36
 
34
- Kitchen::Directions::BakeNoteSubtitle.v1(note: note)
37
+ BakeNoteSubtitle.v1(note: note, cases: cases)
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -1,22 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Kitchen::Directions::BakeNumberedNotes
4
- class V2
5
- def bake(book:, classes:)
6
- classes.each do |klass|
7
- book.chapters.pages.notes("$.#{klass}").each do |note|
8
- note.wrap_children(class: 'os-note-body')
9
- note_count = note.count_in(:page)
10
- note.prepend(child:
11
- <<~HTML
12
- <h3 class="os-title">
13
- <span class="os-title-label">#{note.autogenerated_title}</span>
14
- <span class="os-number">##{note_count}</span>
15
- </h3>
16
- HTML
17
- )
18
- note.exercises.each do |exercise|
19
- Kitchen::Directions::BakeNumberedNotes.bake_note_exercise(note: note, exercise: exercise, divider: '. ')
3
+ module Kitchen::Directions
4
+ module BakeNumberedNotes
5
+ class V2
6
+ def bake(book:, classes:)
7
+ classes.each do |klass|
8
+ book.chapters.pages.notes("$.#{klass}").each do |note|
9
+ note.wrap_children(class: 'os-note-body')
10
+ note_count = note.count_in(:page)
11
+ note.prepend(child:
12
+ <<~HTML
13
+ <h3 class="os-title">
14
+ <span class="os-title-label">#{note.autogenerated_title}</span>
15
+ <span class="os-number">##{note_count}</span>
16
+ </h3>
17
+ HTML
18
+ )
19
+ note.exercises.each do |exercise|
20
+ BakeNumberedNotes.bake_note_exercise(note: note, exercise: exercise, divider: '. ')
21
+ end
22
+ note.injected_questions.each do |question|
23
+ BakeNumberedNotes.bake_note_injected_question(note: note, question: question)
24
+ end
20
25
  end
21
26
  end
22
27
  end